combine_pdf 1.0.20 → 1.0.31

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: b482b2bf36c858f6bb603a66536ee17682b21bc78381fa86a115a4e437f9160c
4
- data.tar.gz: 29ab4a06d5ec597b806feeb346a2e972e98566442606f6e3e765348e5fee8ea4
3
+ metadata.gz: 4c479622755f0a124f3da336eda5869d8591068d30bd408d03c5c6ce2689d12d
4
+ data.tar.gz: 4591c79c64670d11c9693f7edaf61ad2361e6132ba15dc2d5507bf95207f329f
5
5
  SHA512:
6
- metadata.gz: 615c0917cab8ad406d1eb61a81528656e596bc91f88ec96d8662446496bf9e465df917f3fcf1a9aa9ba8b3057e7ec8b64dc7a0799ca53597d3d071e3afc1fd36
7
- data.tar.gz: 045ef9bc9ab9a29a1665df621f1f9f41e2744f6b285b68f6c76ec64e05493f29b423cfb1433c903267e5893d53de71558bf677912551e13fe86ccb66e827f89e
6
+ metadata.gz: c31f00b8ff30ed3fa2927cfc34953b48d152d98771b5a73c4534ea04d0c5f67321ffaac1dae2d052ad9f9458256d2c5d344f62e608f6d31bc923631922606760
7
+ data.tar.gz: 155834559ec9edd5eb4ad6f8935c12bad910918d1316f9a95963e878bf43fab3b035b2da2e5aae185188b86a5ce6261a7f0730933cb6acfe5ad23d48bd98082e
@@ -0,0 +1,32 @@
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", "3.3"]
13
+ rubyopt: [""]
14
+ include:
15
+ - ruby: "3.3"
16
+ rubyopt: "--enable-frozen-string-literal --debug-frozen-string-literal"
17
+
18
+ steps:
19
+ - name: Checkout code
20
+ uses: actions/checkout@v3
21
+
22
+ - name: Setup Ruby
23
+ uses: ruby/setup-ruby@v1
24
+ with:
25
+ ruby-version: ${{ matrix.ruby }}
26
+ bundler-cache: true
27
+
28
+ - name: Generate lockfile
29
+ run: bundle lock
30
+
31
+ - name: Run tests
32
+ run: bundle exec rake test RUBYOPT="${{ matrix.rubyopt }}"
data/.travis.yml ADDED
@@ -0,0 +1 @@
1
+ language: ruby
data/CHANGELOG.md CHANGED
@@ -1,7 +1,58 @@
1
1
  # Change Log
2
2
 
3
- ***
3
+ #### Change log v.1.0.31 (2025-04-08)
4
+
5
+ **Fix**: RangeError: index out of range errors occurred with some malformed PDFs, when the number of bytes in a PDF `stream` didn't match the number of bytes expected according to the `Length` property. Credit to @Laykou and others for opening multiple issues (i.e., #205), as well as @julitrows, @mtwzim, and @Kaiito630, for pushing on this.
6
+
7
+ **Fix**: frozen string literal lingering issues. Credit to @pauline-koch, @qdegraeve, @isaporto, @ncreuschling, @francescob, @@anthonykaufman and @ma-matsui for their input on this issue. Credit to @anthonykaufman for offering one possible solution and @Markus-Munk-Shipmondo for pushing on this. Credit to @mfazekas for opening PR #215 and for @RBIII, @juliolinarez, and @osvaldoalvaradodev for supporting it.
8
+
9
+ **Fix**: possible permission issues. Credit to @davidwessman, @visini, @sander-deryckere, and @LindseySaari for exploring this.
10
+
11
+ **Fix**: calling CombinePDF.parse with a frozen string literal. Credit to @lovro-bikic for offering one possible solution.
12
+
13
+ **Fix**: Ruby 3.4 warning. Credit to @chaadow for offering one possible solution.
14
+
15
+ #### Change log v.1.0.29 (2024-12-07)
16
+
17
+ **Fix**: frozen string literal support fix. Credit to @francescob (Francesco) for PR #245.
18
+
19
+ #### Change log v.1.0.28 (2024-11-12)
20
+
21
+ **Fix**: use `require` to load code (instead of `load`). Credit to @casperisfine (Jean byroot Boussier) for PR #216.
22
+
23
+ #### Change log v.1.0.27 (2024-11-10)
24
+
25
+ **Performance**: fix performance issues with `object_id` usage in Ruby 3+. Credit to @amomchilov (Alexander Momchilov) for PR #241.
26
+
27
+ **Performance**: use frozen string literals. Credit to @casperisfine (Jean byroot Boussier) for PR #239.
28
+
29
+ #### Change log v.1.0.26 (2023-12-22)
30
+
31
+ **Performance**: possible performance bump. Credit to @denislavski (Denislav Naydenov) for opening PR #235.
32
+
33
+ #### Change log v.1.0.25 (2023-12-19)
34
+
35
+ **Fix**: possible improve memory usage. Credit to @denislavski (Denislav Naydenov) for opening PR #233 and suggesting this change.
36
+
37
+ #### Change log v.1.0.24 (2023-10-19)
38
+
39
+ **Fix**: possible `nil` in loop. Credit to @jkowens for PR #231 and adding a quick fix using a simple guard.
40
+
41
+ **Fix**: preserve file creation date metadata where relevant.
42
+
43
+ #### Change log v.1.0.23 (2023-04-04)
44
+
45
+ **Feature**: merged PR #177 for the `raise_on_encrypted: true` option support. Credit to @leviwilson and @kimyu92 for the PR.
46
+
47
+ #### Change log v.1.0.22
48
+
49
+ **Fix**: fix `fonts` dereferencing issue (#203), credit to @MarcWeber (Marc Weber) for identifying the issue.
50
+
51
+ **Fix**: fix `metrix` dependency, credit to @casperisfine (Jean byroot Boussier) for PR #195.
52
+
53
+ #### Change log v.1.0.21
4
54
 
55
+ **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.
5
56
 
6
57
  #### Change log v.1.0.20
7
58
 
data/README.md CHANGED
@@ -7,23 +7,23 @@
7
7
 
8
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).
9
9
 
10
- ## Install
10
+ ## Unmaintained - Help Wanted(!)
11
11
 
12
- Install with ruby gems:
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).
13
13
 
14
- ```ruby
15
- gem install combine_pdf
16
- ```
14
+ I wrote this gem because I needed to solve an issue with bates-numbering existing PDF documents.
17
15
 
18
- ## Help Wanted
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.
19
17
 
20
- I need help maintaining the CombinePDF Ruby gem.
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.
21
19
 
22
- I wrote this gem because I needed to solve an issue with bates-numbering existing PDF documents. However, during the last three years or so I have been maintaining the project for no reason at all, except that I enjoyed sharing it with the community.
20
+ ## Install
23
21
 
24
- I love this gem, but I feel it's time I took a step back from maintaining it and concentrate on my music and other things I want to develop.
22
+ Install with ruby gems:
25
23
 
26
- Please hit me up if you would like to join in and eventually take over.
24
+ ```ruby
25
+ gem install combine_pdf
26
+ ```
27
27
 
28
28
  ## Known Limitations
29
29
 
@@ -39,7 +39,7 @@ Quick rundown:
39
39
 
40
40
  Some links will be lost when ripping pages out of PDF files and merging them with another PDF.
41
41
 
42
- * 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`.
43
43
 
44
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)`.
45
45
 
data/Rakefile CHANGED
@@ -1,2 +1,9 @@
1
- require "bundler/gem_tasks"
1
+ require 'bundler/gem_tasks'
2
+ require 'rake/testtask'
3
+
4
+ task default: 'test'
5
+
6
+ Rake::TestTask.new do |t|
7
+ t.test_files = FileList['test/**/*_test.rb']
8
+ end
2
9
 
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 = ["boaz@2be.co.il"]
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
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
@@ -1,4 +1,5 @@
1
1
  # -*- encoding : utf-8 -*-
2
+ # frozen_string_literal: true
2
3
 
3
4
  module CombinePDF
4
5
  module_function
@@ -1,4 +1,5 @@
1
1
  # -*- encoding : utf-8 -*-
2
+ # frozen_string_literal: true
2
3
  ########################################################
3
4
  ## Thoughts from reading the ISO 32000-1:2008
4
5
  ## this file is part of the CombinePDF library and the code
@@ -35,7 +36,7 @@ module CombinePDF
35
36
  # mediabox:: the PDF page size in PDF points. defaults to [0, 0, 612.0, 792.0] (US Letter)
36
37
  def initialize(mediabox = [0, 0, 612.0, 792.0])
37
38
  # indirect_reference_id, :indirect_generation_number
38
- @contents = ''
39
+ @contents = String.new
39
40
  @base_font_name = 'Writer' + SecureRandom.hex(7) + 'PDF'
40
41
  self[:Type] = :Page
41
42
  self[:indirect_reference_id] = 0
@@ -1,4 +1,5 @@
1
1
  # -*- encoding : utf-8 -*-
2
+ # frozen_string_literal: true
2
3
  ########################################################
3
4
  ## Thoughts from reading the ISO 32000-1:2008
4
5
  ## this file is part of the CombinePDF library and the code
@@ -137,7 +138,7 @@ module CombinePDF
137
138
  object_key = @key.dup
138
139
  object_key << [encrypted_id].pack('i')[0..2]
139
140
  object_key << [encrypted_generation].pack('i')[0..1]
140
- object_key << 'sAlT'.force_encoding(Encoding::ASCII_8BIT)
141
+ object_key << 'sAlT'.b
141
142
  key_length = object_key.length < 16 ? object_key.length : 16
142
143
 
143
144
  begin
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module CombinePDF
2
4
  class EncryptionError < StandardError
3
5
  end
@@ -1,4 +1,5 @@
1
1
  # -*- encoding : utf-8 -*-
2
+ # frozen_string_literal: true
2
3
  ########################################################
3
4
  ## Thoughts from reading the ISO 32000-1:2008
4
5
  ## this file is part of the CombinePDF library and the code
@@ -1,4 +1,5 @@
1
1
  # -*- encoding : utf-8 -*-
2
+ # frozen_string_literal: true
2
3
  ########################################################
3
4
  ## Thoughts from reading the ISO 32000-1:2008
4
5
  ## this file is part of the CombinePDF library and the code
@@ -1,4 +1,5 @@
1
1
  # -*- encoding : utf-8 -*-
2
+ # frozen_string_literal: true
2
3
  ########################################################
3
4
  ## Thoughts from reading the ISO 32000-1:2008
4
5
  ## this file is part of the CombinePDF library and the code
@@ -214,7 +215,7 @@ module CombinePDF
214
215
  options[:text_padding] = 0 if options[:text_padding].to_f >= 1
215
216
 
216
217
  # create box stream
217
- box_stream = ''
218
+ box_stream = +''
218
219
  # set graphic state for box
219
220
  if options[:box_color] || (options[:border_width].to_i > 0 && options[:border_color])
220
221
  # compute x and y position for text
@@ -290,7 +291,7 @@ module CombinePDF
290
291
  # reset x,y by text alignment - x,y are calculated from the bottom left
291
292
  # each unit (1) is 1/72 Inch
292
293
  # create text stream
293
- text_stream = ''
294
+ text_stream = +''
294
295
  if !text.to_s.empty? && options[:font_size] != 0 && (options[:font_color] || options[:stroke_color])
295
296
  # compute x and y position for text
296
297
  x = options[:x] + (options[:width] * options[:text_padding])
@@ -679,7 +680,7 @@ module CombinePDF
679
680
  insert_content 'Q'
680
681
 
681
682
  # Prep content
682
- @contents = ''
683
+ @contents = +''
683
684
  insert_content @contents
684
685
  @contents
685
686
  end
@@ -788,7 +789,7 @@ module CombinePDF
788
789
  # add to array
789
790
  if out.last.nil? || out.last[0] != fonts[i]
790
791
  out.last[1] << '>' unless out.last.nil?
791
- out << [fonts[i], '<', 0, 0]
792
+ out << [fonts[i], (+'<'), 0, 0]
792
793
  end
793
794
  out.last[1] << (fonts_array[i].cmap.nil? ? (c.unpack('H*')[0]) : fonts_array[i].cmap[c])
794
795
  if fonts_array[i].metrics[c]
@@ -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>
@@ -41,7 +41,7 @@ module CombinePDF
41
41
  # string:: the data to be parsed, as a String object.
42
42
  def initialize(string, options = {})
43
43
  raise TypeError, "couldn't parse data, expecting type String" unless string.is_a? String
44
- @string_to_parse = string.force_encoding(Encoding::ASCII_8BIT)
44
+ @string_to_parse = (string.frozen? ? string.dup : string).force_encoding(Encoding::ASCII_8BIT)
45
45
  @literal_strings = [].dup
46
46
  @hex_strings = [].dup
47
47
  @streams = [].dup
@@ -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
@@ -260,7 +262,7 @@ module CombinePDF
260
262
  ##########################################
261
263
  elsif @scanner.scan(/\(/)
262
264
  # warn "Found a literal string"
263
- str = ''.force_encoding(Encoding::ASCII_8BIT)
265
+ str = ''.b
264
266
  count = 1
265
267
  while count > 0 && @scanner.rest?
266
268
  scn = @scanner.scan_until(/[\(\)]/)
@@ -321,8 +323,8 @@ module CombinePDF
321
323
  str << 12
322
324
  when 48..57 # octal notation for byte?
323
325
  rep -= 48
324
- rep = (rep << 3) + (str_bytes.shift-48) if str_bytes[0].between?(48, 57)
325
- rep = (rep << 3) + (str_bytes.shift-48) if str_bytes[0].between?(48, 57) && (((rep << 3) + (str_bytes[0] - 48)) <= 255)
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)
326
328
  str << rep
327
329
  when 10 # new line, ignore
328
330
  str_bytes.shift if str_bytes[0] == 13
@@ -356,27 +358,39 @@ module CombinePDF
356
358
  ##########################################
357
359
  ## parse a Stream
358
360
  ##########################################
359
- elsif @scanner.scan(/stream[ \t]*[\r\n]/)
360
- @scanner.pos += 1 if @scanner.peek(1) == "\n".freeze && @scanner.matched[-1] != "\n".freeze
361
- # the following was dicarded because some PDF files didn't have an EOL marker as required
362
- # str = @scanner.scan_until(/(\r\n|\r|\n)endstream/)
363
- # instead, a non-strict RegExp is used:
364
- str = @scanner.scan_until(/endstream/)
365
-
366
- # raise error if the stream doesn't end.
367
- unless str
368
- raise ParsingError, "Parsing Error: PDF file error - a stream object wasn't properly closed using 'endstream'!"
361
+ elsif @scanner.scan(/stream[ \t]*\r?\n?/)
362
+ # advance by the publshed stream length (if any)
363
+ old_pos = @scanner.pos
364
+ if(out.last.is_a?(Hash) && out.last[:Length].is_a?(Integer) && out.last[:Length])
365
+ @scanner.pos += out.last[:Length]
366
+ unless(@scanner.skip(/\r?\n?endstream/))
367
+ @scanner.pos = old_pos
368
+ # raise error if the stream doesn't end.
369
+ unless @scanner.skip_until(/endstream/)
370
+ raise ParsingError, "Parsing Error: PDF file error - a stream object wasn't properly closed using 'endstream'!"
371
+ end
372
+ end
373
+ else
374
+ # raise error if the stream doesn't end.
375
+ unless @scanner.skip_until(/endstream/)
376
+ raise ParsingError, "Parsing Error: PDF file error - a stream object wasn't properly closed using 'endstream'!"
377
+ end
369
378
  end
370
379
 
380
+ length = @scanner.pos - (old_pos + 9)
381
+ length = 0 if(length < 0)
382
+ length -= 1 if(@scanner.string[old_pos + length - 1] == "\n")
383
+ length -= 1 if(@scanner.string[old_pos + length - 1] == "\r")
384
+ str = (length > 0) ? @scanner.string.slice(old_pos, length) : +''
385
+
371
386
  # warn "CombinePDF parser: detected Stream #{str.length} bytes long #{str[0..3]}...#{str[-4..-1]}"
372
387
 
373
388
  # need to remove end of stream
374
389
  if out.last.is_a? Hash
375
- # out.last[:raw_stream_content] = str[0...-10] #cuts only one EON char (\n or \r)
376
- out.last[:raw_stream_content] = unify_string str.sub(/(\r\n|\n|\r)?endstream\z/, '').force_encoding(Encoding::ASCII_8BIT)
390
+ out.last[:raw_stream_content] = unify_string str.force_encoding(Encoding::ASCII_8BIT)
377
391
  else
378
392
  warn 'Stream not attached to dictionary!'
379
- out << str.sub(/(\r\n|\n|\r)?endstream\z/, '').force_encoding(Encoding::ASCII_8BIT)
393
+ out << str.force_encoding(Encoding::ASCII_8BIT)
380
394
  end
381
395
  ##########################################
382
396
  ## parse an Object after finished
@@ -620,17 +634,17 @@ module CombinePDF
620
634
  #
621
635
  def serialize_objects_and_references
622
636
  obj_dir = {}
623
- objid_cache = {}
637
+ objid_cache = {}.compare_by_identity
624
638
  # create a dictionary for referenced objects (no value resolution at this point)
625
639
  # at the same time, delete duplicates and old versions when objects have multiple versions
626
640
  @parsed.uniq!
627
641
  @parsed.length.times do |i|
628
642
  o = @parsed[i]
629
- objid_cache[o.object_id] = i
643
+ objid_cache[o] = i
630
644
  tmp_key = [o[:indirect_reference_id], o[:indirect_generation_number]]
631
645
  if tmp_found = obj_dir[tmp_key]
632
646
  tmp_found.clear
633
- @parsed[objid_cache[tmp_found.object_id]] = nil
647
+ @parsed[objid_cache[tmp_found]] = nil
634
648
  end
635
649
  obj_dir[tmp_key] = o
636
650
  end
@@ -712,6 +726,7 @@ module CombinePDF
712
726
 
713
727
  # All Strings are one String
714
728
  def unify_string(str)
729
+ str = str.dup if(str.frozen?)
715
730
  str.force_encoding(Encoding::ASCII_8BIT)
716
731
  @strings_dictionary[str] ||= str
717
732
  end
@@ -753,9 +768,9 @@ module CombinePDF
753
768
  # end
754
769
 
755
770
  # # run block of code on evey PDF object (PDF objects are class Hash)
756
- # def each_object(object, limit_references = true, already_visited = {}, &block)
771
+ # def each_object(object, limit_references = true, already_visited = {}.compare_by_identity, &block)
757
772
  # unless limit_references
758
- # already_visited[object.object_id] = true
773
+ # already_visited[object] = true
759
774
  # end
760
775
  # case
761
776
  # when object.is_a?(Array)
@@ -764,7 +779,7 @@ module CombinePDF
764
779
  # yield(object)
765
780
  # unless limit_references && object[:is_reference_only]
766
781
  # object.each do |k,v|
767
- # each_object(v, limit_references, already_visited, &block) unless already_visited[v.object_id]
782
+ # each_object(v, limit_references, already_visited, &block) unless already_visited[v]
768
783
  # end
769
784
  # end
770
785
  # end
@@ -1,4 +1,5 @@
1
1
  # -*- encoding : utf-8 -*-
2
+ # frozen_string_literal: true
2
3
  ########################################################
3
4
  ## Thoughts from reading the ISO 32000-1:2008
4
5
  ## this file is part of the CombinePDF library and the code
@@ -21,19 +22,19 @@ module CombinePDF
21
22
  # this is used for internal operations, such as injectng data using the << operator.
22
23
  def add_referenced()
23
24
  # an existing object map
24
- resolved = {}.dup
25
- existing = {}.dup
26
- should_resolve = [].dup
25
+ resolved = {}.compare_by_identity
26
+ existing = {}
27
+ should_resolve = []
27
28
  #set all existing objects as resolved and register their children for future resolution
28
- @objects.each { |obj| existing[obj] = obj ; resolved[obj.object_id] = obj; should_resolve << obj.values}
29
+ @objects.each { |obj| existing[obj] = obj ; resolved[obj] = obj; should_resolve << obj.values}
29
30
  # loop until should_resolve is empty
30
31
  while should_resolve.any?
31
32
  obj = should_resolve.pop
32
- next if resolved[obj.object_id] # the object exists
33
+ next if resolved[obj] # the object exists
33
34
  if obj.is_a?(Hash)
34
35
  referenced = obj[:referenced_object]
35
36
  if referenced && referenced.any?
36
- tmp = resolved[referenced.object_id]
37
+ tmp = resolved[referenced]
37
38
  if !tmp && referenced[:raw_stream_content]
38
39
  tmp = existing[referenced[:raw_stream_content]]
39
40
  # Avoid endless recursion by limiting it to a number of layers (default == 2)
@@ -42,18 +43,18 @@ module CombinePDF
42
43
  if tmp
43
44
  obj[:referenced_object] = tmp
44
45
  else
45
- resolved[obj.object_id] = referenced
46
+ resolved[obj] = referenced
46
47
  # existing[referenced] = referenced
47
48
  existing[referenced[:raw_stream_content]] = referenced
48
49
  should_resolve << referenced
49
50
  @objects << referenced
50
51
  end
51
52
  else
52
- resolved[obj.object_id] = obj
53
- obj.keys.each { |k| should_resolve << obj[k] unless !obj[k].is_a?(Enumerable) || resolved[obj[k].object_id] }
53
+ resolved[obj] = obj
54
+ obj.keys.each { |k| should_resolve << obj[k] unless !obj[k].is_a?(Enumerable) || resolved[obj[k]] }
54
55
  end
55
56
  elsif obj.is_a?(Array)
56
- resolved[obj.object_id] = obj
57
+ resolved[obj] = obj
57
58
  should_resolve.concat obj
58
59
  end
59
60
  end
@@ -78,14 +79,14 @@ module CombinePDF
78
79
  page_list.concat(with_pages) unless with_pages.empty?
79
80
 
80
81
  # duplicate any non-unique pages - This is a special case to resolve Adobe Acrobat Reader issues (see issues #19 and #81)
81
- uniqueness = {}.dup
82
- page_list.each { |page| page = page[:referenced_object] || page; page = page.dup if uniqueness[page.object_id]; uniqueness[page.object_id] = page }
82
+ uniqueness = {}.compare_by_identity
83
+ page_list.each { |page| page = page[:referenced_object] || page; page = page.dup if uniqueness[page]; uniqueness[page] = page }
83
84
  page_list.clear
84
85
  page_list = uniqueness.values
85
86
  uniqueness.clear
86
87
 
87
88
  # build new Pages object
88
- page_object_kids = [].dup
89
+ page_object_kids = []
89
90
  pages_object = { Type: :Pages, Count: page_list.length, Kids: page_object_kids }
90
91
  pages_object_reference = { referenced_object: pages_object, is_reference_only: true }
91
92
  page_list.each { |pg| pg[:Parent] = pages_object_reference; page_object_kids << ({ referenced_object: pg, is_reference_only: true }) }
@@ -186,17 +187,18 @@ module CombinePDF
186
187
  POSSIBLE_NAME_TREES = [:Dests, :AP, :Pages, :IDS, :Templates, :URLS, :JavaScript, :EmbeddedFiles, :AlternatePresentations, :Renditions].to_set.freeze
187
188
 
188
189
  def rebuild_names(name_tree = nil, base = 'CombinePDF_0000000')
190
+ base = +base
189
191
  if name_tree
190
192
  return nil unless name_tree.is_a?(Hash)
191
193
  name_tree = name_tree[:referenced_object] || name_tree
192
194
  dic = []
193
195
  # map a names tree and return a valid name tree. Do not recourse.
194
196
  should_resolve = [name_tree[:Kids], name_tree[:Names]]
195
- resolved = [].to_set
197
+ resolved = Set.new.compare_by_identity
196
198
  while should_resolve.any?
197
199
  pos = should_resolve.pop
198
200
  if pos.is_a? Array
199
- next if resolved.include?(pos.object_id)
201
+ next if resolved.include?(pos)
200
202
  if pos[0].is_a? String
201
203
  (pos.length / 2).times do |i|
202
204
  dic << (pos[i * 2].clear << base.next!)
@@ -209,16 +211,16 @@ module CombinePDF
209
211
  end
210
212
  elsif pos.is_a? Hash
211
213
  pos = pos[:referenced_object] || pos
212
- next if resolved.include?(pos.object_id)
214
+ next if resolved.include?(pos)
213
215
  should_resolve << pos[:Kids] if pos[:Kids]
214
216
  should_resolve << pos[:Names] if pos[:Names]
215
217
  end
216
- resolved << pos.object_id
218
+ resolved << pos
217
219
  end
218
220
  return { referenced_object: { Names: dic }, is_reference_only: true }
219
221
  end
220
222
  @names ||= @names[:referenced_object]
221
- new_names = { Type: :Names }.dup
223
+ new_names = { Type: :Names }
222
224
  POSSIBLE_NAME_TREES.each do |ntree|
223
225
  if @names[ntree]
224
226
  new_names[ntree] = rebuild_names(@names[ntree], base)
@@ -373,7 +375,7 @@ module CombinePDF
373
375
  private
374
376
 
375
377
  def equal_layers obj1, obj2, layer = CombinePDF.eq_depth_limit
376
- return true if obj1.object_id == obj2.object_id
378
+ return true if obj1.equal?(obj2)
377
379
  if obj1.is_a? Hash
378
380
  return false unless obj2.is_a? Hash
379
381
  return false unless obj1.length == obj2.length
@@ -1,5 +1,6 @@
1
1
  # -*- encoding : utf-8 -*-
2
- ########################################################
2
+ ## frozen_string_literal: true
3
+ #######################################################
3
4
  ## Thoughts from reading the ISO 32000-1:2008
4
5
  ## this file is part of the CombinePDF library and the code
5
6
  ## is subject to the same license.
@@ -93,9 +94,10 @@ module CombinePDF
93
94
  @version = 0
94
95
  @viewer_preferences = {}
95
96
  @info = {}
96
- parser ||= PDFParser.new('')
97
+ parser ||= PDFParser.new(+'')
97
98
  raise TypeError, "initialization error, expecting CombinePDF::PDFParser or nil, but got #{parser.class.name}" unless parser.is_a? PDFParser
98
99
  @objects = parser.parse
100
+
99
101
  # remove any existing id's
100
102
  remove_old_ids
101
103
  # set data from parser
@@ -174,8 +176,12 @@ module CombinePDF
174
176
  def to_pdf(options = {})
175
177
  # reset version if not specified
176
178
  @version = 1.5 if @version.to_f == 0.0
179
+
177
180
  # set info for merged file
178
- @info[:ModDate] = @info[:CreationDate] = Time.now.strftime "D:%Y%m%d%H%M%S%:::z'00"
181
+ unless(@info[:CreationDate].is_a?(String))
182
+ @info[:CreationDate] = Time.now unless @info[:CreationDate].is_a?(Time)
183
+ @info[:CreationDate] = @info[:CreationDate].getgm.strftime("D:%Y%m%d%H%M%S%:::z'00")
184
+ end
179
185
  @info[:Subject] = options[:subject] if options[:subject]
180
186
  @info[:Producer] = options[:producer] if options[:producer]
181
187
  # rebuild_catalog
@@ -201,9 +207,9 @@ module CombinePDF
201
207
  xref_location = loc
202
208
  # xref_location = 0
203
209
  # out.each { |line| xref_location += line.bytesize + 1}
204
- out << "xref\n0 #{indirect_object_count}\n0000000000 65535 f \n"
205
- xref.each { |offset| out << (out.pop + ("%010d 00000 n \n" % offset)) }
206
- out << out.pop + 'trailer'
210
+ out << "xref\n0 #{indirect_object_count}\n0000000000 65535 f "
211
+ xref.each { |offset| out << ("%010d 00000 n ".freeze % offset) }
212
+ out << 'trailer'.freeze
207
213
  out << "<<\n/Root #{false || "#{catalog[:indirect_reference_id]} #{catalog[:indirect_generation_number]} R"}"
208
214
  out << "/Size #{indirect_object_count}"
209
215
  out << "/Info #{@info[:indirect_reference_id]} #{@info[:indirect_generation_number]} R"
@@ -211,7 +217,7 @@ module CombinePDF
211
217
  # when finished, remove the numbering system and keep only pointers
212
218
  remove_old_ids
213
219
  # output the pdf stream
214
- out.join("\n".force_encoding(Encoding::ASCII_8BIT)).force_encoding(Encoding::ASCII_8BIT)
220
+ out.join("\n".b).force_encoding(Encoding::ASCII_8BIT)
215
221
  end
216
222
 
217
223
  # this method returns all the pages cataloged in the catalog.
@@ -257,12 +263,18 @@ module CombinePDF
257
263
  def fonts(limit_to_type0 = false)
258
264
  fonts_array = []
259
265
  pages.each do |pg|
260
- if pg[:Resources][:Font]
261
- pg[:Resources][:Font].values.each do |f|
262
- f = f[:referenced_object] if f[:referenced_object]
263
- if (limit_to_type0 || f[:Subtype] == :Type0) && f[:Type] == :Font && !fonts_array.include?(f)
264
- fonts_array << f
265
- end
266
+ r = pg[:Resources]
267
+ next if !r
268
+ r = r[:referenced_object] if r[:referenced_object]
269
+ r = r[:Font]
270
+ next if !r
271
+ r = r[:referenced_object] if r[:referenced_object]
272
+ r.values.each do |f|
273
+ next if f.class != Hash
274
+ f = f[:referenced_object] if f[:referenced_object]
275
+ next if f.class != Hash
276
+ if (limit_to_type0 || f[:Subtype] == :Type0) && f[:Type] == :Font && !fonts_array.include?(f)
277
+ fonts_array << f
266
278
  end
267
279
  end
268
280
  end
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  module CombinePDF
2
3
  ################################################################
3
4
  ## These are common functions, used within the different classes
@@ -51,7 +52,7 @@ module CombinePDF
51
52
  if object.length == 0 || obj_bytes.min <= 31 || obj_bytes.max >= 127 # || (obj_bytes[0] != 68 object.match(/[^D\:\d\+\-Z\']/))
52
53
  # A hexadecimal string shall be written as a sequence of hexadecimal digits (0-9 and either A-F or a-f)
53
54
  # encoded as ASCII characters and enclosed within angle brackets (using LESS-THAN SIGN (3Ch) and GREATER- THAN SIGN (3Eh)).
54
- "<#{object.unpack('H*')[0]}>".force_encoding(Encoding::ASCII_8BIT)
55
+ "<#{object.unpack('H*')[0]}>"
55
56
  else
56
57
  # a good fit for a Literal String or the string is a date (MUST be literal)
57
58
  ('(' + ([].tap { |out| obj_bytes.each { |byte| out.concat(STRING_REPLACEMENT_ARRAY[byte]) } } ).pack('C*') + ')').force_encoding(Encoding::ASCII_8BIT)
@@ -100,7 +101,7 @@ module CombinePDF
100
101
  end
101
102
  object[:indirect_reference_id] ||= 0
102
103
  object[:indirect_generation_number] ||= 0
103
- return "#{object[:indirect_reference_id]} #{object[:indirect_generation_number]} R".force_encoding(Encoding::ASCII_8BIT)
104
+ return "#{object[:indirect_reference_id]} #{object[:indirect_generation_number]} R"
104
105
  end
105
106
 
106
107
  # if the object is indirect...
@@ -108,7 +109,7 @@ module CombinePDF
108
109
  if object[:indirect_reference_id]
109
110
  object[:indirect_reference_id] ||= 0
110
111
  object[:indirect_generation_number] ||= 0
111
- out << "#{object[:indirect_reference_id]} #{object[:indirect_generation_number]} obj\n".force_encoding(Encoding::ASCII_8BIT)
112
+ out << "#{object[:indirect_reference_id]} #{object[:indirect_generation_number]} obj\n"
112
113
  if object[:indirect_without_dictionary]
113
114
  out << object_to_pdf(object[:indirect_without_dictionary])
114
115
  out << "\nendobj\n"
@@ -123,13 +124,13 @@ module CombinePDF
123
124
  # if the object is not a simple object, it is a dictionary
124
125
  # A dictionary shall be written as a sequence of key-value pairs enclosed in double angle brackets (<<...>>)
125
126
  # (using LESS-THAN SIGNs (3Ch) and GREATER-THAN SIGNs (3Eh)).
126
- out << "<<\n".force_encoding(Encoding::ASCII_8BIT)
127
+ out << "<<\n".b
127
128
  object.each do |key, value|
128
- out << "#{object_to_pdf key} #{object_to_pdf value}\n".force_encoding(Encoding::ASCII_8BIT) unless PDF::PRIVATE_HASH_KEYS.include? key
129
+ out << "#{object_to_pdf key} #{object_to_pdf value}\n" unless PDF::PRIVATE_HASH_KEYS.include? key
129
130
  end
130
131
  object.delete :Length
131
- out << '>>'.force_encoding(Encoding::ASCII_8BIT)
132
- out << "\nstream\n#{object[:raw_stream_content]}\nendstream".force_encoding(Encoding::ASCII_8BIT) if object[:raw_stream_content]
132
+ out << '>>'.b
133
+ out << "\nstream\n#{object[:raw_stream_content]}\nendstream" if object[:raw_stream_content]
133
134
  out << "\nendobj\n" if object[:indirect_reference_id]
134
135
  out.join.force_encoding(Encoding::ASCII_8BIT)
135
136
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module CombinePDF
2
- VERSION = '1.0.20'.freeze
4
+ VERSION = '1.0.31'
3
5
  end
data/lib/combine_pdf.rb CHANGED
@@ -1,4 +1,5 @@
1
1
  # -*- encoding : utf-8 -*-
2
+ # frozen_string_literal: true
2
3
 
3
4
  require 'zlib'
4
5
  require 'securerandom'
@@ -10,21 +11,21 @@ require 'digest'
10
11
  # require the RC4 Gem
11
12
  require 'rc4'
12
13
 
13
- load 'combine_pdf/api.rb'
14
- load 'combine_pdf/renderer.rb'
15
- load 'combine_pdf/page_methods.rb'
16
- load 'combine_pdf/basic_writer.rb'
17
- load 'combine_pdf/decrypt.rb'
18
- load 'combine_pdf/fonts.rb'
19
- load 'combine_pdf/filter.rb'
20
- load 'combine_pdf/parser.rb'
21
- load 'combine_pdf/pdf_public.rb'
22
- load 'combine_pdf/pdf_protected.rb'
23
- load 'combine_pdf/exceptions.rb'
14
+ require 'combine_pdf/api'
15
+ require 'combine_pdf/renderer'
16
+ require 'combine_pdf/page_methods'
17
+ require 'combine_pdf/basic_writer'
18
+ require 'combine_pdf/decrypt'
19
+ require 'combine_pdf/fonts'
20
+ require 'combine_pdf/filter'
21
+ require 'combine_pdf/parser'
22
+ require 'combine_pdf/pdf_public'
23
+ require 'combine_pdf/pdf_protected'
24
+ require 'combine_pdf/exceptions'
24
25
 
25
- # load "combine_pdf/operations.rb"
26
+ # require 'combine_pdf/operations'
26
27
 
27
- load 'combine_pdf/version.rb'
28
+ require 'combine_pdf/version'
28
29
 
29
30
  # This is a pure ruby library to combine/merge, stmap/overlay and number PDF files - as well as to create tables (ment for indexing combined files).
30
31
  #
data/test/automated CHANGED
@@ -3,9 +3,10 @@
3
3
  $VERBOSE = true
4
4
 
5
5
  require 'benchmark'
6
- $LOAD_PATH.unshift File.expand_path(File.join('..', '..', 'lib'), __FILE__)
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 "./Ruby/test\ pdfs/filled_form.pdf"
18
- pdf.save '01_check_radio_buttuns.pdf'
19
- pdf = CombinePDF.load "./Ruby/test\ pdfs/filled_form.pdf"
20
- pdf << CombinePDF.load("./Ruby/test\ pdfs/empty_form.pdf")
21
- pdf << CombinePDF.load("./Ruby/test\ pdfs/filled_form.pdf")
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 "./Ruby/test\ pdfs/check_form_data__objstreams_w_versions.pdf"
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 './Ruby/test pdfs/share-font-background.pdf'
29
- pdf2 = CombinePDF.load './Ruby/test pdfs/share-font-foreground.pdf'
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 './Ruby/test pdfs/nil_1.pdf'
35
- pdf2 = CombinePDF.load './Ruby/test pdfs/nil_2.pdf'
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 './Ruby/test pdfs/space_after_streram_keyword.pdf'
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 './Ruby/test pdfs/nested_difference.pdf'
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 './Ruby/test pdfs/names_go_haywire_0.pdf'
46
- pdf << CombinePDF.load('./Ruby/test pdfs/names_go_haywire_1.pdf')
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('./Ruby/test pdfs/outlines/self_merge_err.pdf')
50
- pdf.save '05_x1_scribus_test.pdf'
51
- pdf = CombinePDF.load('./Ruby/test pdfs/outlines/self_merge_err.pdf')
52
- pdf << CombinePDF.load('./Ruby/test pdfs/outlines/self_merge_err.pdf')
53
- pdf.save '05_x2_scribus_test.pdf'
54
- # pdf = CombinePDF.load "./Ruby/test pdfs/named_dest.pdf";nil
55
- # pdf.save '05_check_named_dest_links.pdf' # this will take a while
56
- # pdf = CombinePDF.load "./Ruby/test pdfs/named_dest.pdf";nil
57
- pdf << CombinePDF.load('./Ruby/test pdfs/named_dest.pdf'); nil
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 './Ruby/test pdfs/outline_small.pdf'
61
- pdf << CombinePDF.load('./Ruby/test pdfs/outline_small.pdf')
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(./Ruby/test\ pdfs/outlines/self_merge_err.pdf ./Ruby/test\ pdfs/outlines/big_toc.pdf ./Ruby/test\ pdfs/outlines/bigger_toc.pdf ./Ruby/test\ pdfs/outlines/named_dest_no_toc.pdf ./Ruby/test\ pdfs/outlines/named_dest_no_toc2.pdf ./Ruby/test\ pdfs/outlines/named_dest.pdf ./Ruby/test\ pdfs/outlines/named_dest2.pdf)
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("./Ruby/test\ pdfs/Scribus-unknown_err.pdf").save '08_1-unknown-err-empty-str.pdf'
96
- CombinePDF.load("./Ruby/test\ pdfs/Scribus-unknown_err2.pdf").save '08_2-unknown-err-empty-str.pdf'
97
- CombinePDF.load("./Ruby/test\ pdfs/Scribus-unknown_err3.pdf").save '08_3-unknown-err-empty-str.pdf'
98
- CombinePDF.load("./Ruby/test\ pdfs/xref_in_middle.pdf").save '08_4-xref-in-middle.pdf'
99
- CombinePDF.load("./Ruby/test\ pdfs/xref_split.pdf").save '08_5-xref-fragmented.pdf'
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("/Users/2Be/Ruby/test\ pdfs/nil_object.pdf").save('09_nil_in_parsed_array.pdf')
102
+ CombinePDF.load("../../test\ pdfs/nil_object.pdf").save('../tmp/09_nil_in_parsed_array.pdf')
102
103
 
103
- encrypted = [ "./Ruby/test\ pdfs/pdf-reader/encrypted_version4_revision4_128bit_aes_user_pass_apples_enc_metadata.pdf",
104
- "./Ruby/test\ pdfs/AESv2\ encrypted.pdf",
105
- "./Ruby/test\ pdfs/pdf-reader/encrypted_version2_revision3_128bit_rc4_blank_user_pass.pdf",
106
- "./Ruby/test\ pdfs/AES\ enc.pdf",
107
- "./Ruby/test\ pdfs/RC4\ enc.pdf"]
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
- # "./Ruby/test\ pdfs/AESv2\ encrypted.pdf",
136
- # "./Ruby/test\ pdfs/data-in-comment.pdf",
137
- # "./Ruby/test\ pdfs/file_name.pdf",
138
- # "./Ruby/test\ pdfs/garbage_after_eof.pdf",
139
- # "./Ruby/test\ pdfs/Many\ comments.pdf",
140
- # "./Ruby/test\ pdfs/nested\ contents\ array.PDF",
141
- # "./Ruby/test\ pdfs/nested_resources.pdf",
142
- # "./Ruby/test\ pdfs/original-missing-endobje.pdf",
143
- # "./Ruby/test\ pdfs/original-multi-issue.pdf",
144
- # "./Ruby/test\ pdfs/page_stap_nil_secure.pdf",
145
- # "./Ruby/test\ pdfs/referenced\ decryption.pdf",
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
@@ -1,6 +1,4 @@
1
- require 'bundler/setup'
2
1
  require 'minitest/autorun'
3
- require 'combine_pdf/renderer'
4
2
 
5
3
  class CombinePDFRendererTest < Minitest::Test
6
4
 
@@ -14,7 +12,7 @@ class CombinePDFRendererTest < Minitest::Test
14
12
 
15
13
  def test_numeric_array_to_pdf
16
14
  input = [1.234567, 0.000054, 5, -0.000099]
17
- expected = "[1.234567 0.000054 5 -0.000099]".force_encoding('BINARY')
15
+ expected = "[1.234567 0.000054 5 -0.000099]".b
18
16
  actual = TestRenderer.new.test_object(input)
19
17
 
20
18
  assert_equal(expected, actual)
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.20
4
+ version: 1.0.31
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: 2020-12-07 00:00:00.000000000 Z
11
+ date: 2025-04-08 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: ruby-rc4
@@ -24,6 +24,20 @@ 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
@@ -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
- - boaz@2be.co.il
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.1.2
108
- signing_key:
140
+ rubygems_version: 3.5.22
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