diff --git a/lib/pdf/reader/object_hash.rb b/lib/pdf/reader/object_hash.rb index 848ce62c..d4d17057 100644 --- a/lib/pdf/reader/object_hash.rb +++ b/lib/pdf/reader/object_hash.rb @@ -2,6 +2,8 @@ # typed: true # frozen_string_literal: true +require 'tempfile' + class PDF::Reader # Provides low level access to the objects in a PDF file via a hash-like # object. @@ -597,12 +599,12 @@ def read_version end def extract_io_from(input) - if input.is_a?(IO) || input.is_a?(StringIO) + if input.is_a?(IO) || input.is_a?(StringIO) || input.is_a?(Tempfile) input elsif File.file?(input.to_s) StringIO.new read_as_binary(input.to_s) else - raise ArgumentError, "input must be an IO-like object or a filename" + raise ArgumentError, "input must be an IO-like object or a filename (#{input.class})" end end diff --git a/rbi/pdf-reader.rbi b/rbi/pdf-reader.rbi index 3ddff682..d0de85b9 100644 --- a/rbi/pdf-reader.rbi +++ b/rbi/pdf-reader.rbi @@ -4,7 +4,7 @@ module PDF sig { returns(PDF::Reader::ObjectHash) } attr_reader :objects - sig { params(input: T.any(String, IO), opts: T::Hash[T.untyped, T.untyped]).void } + sig { params(input: T.any(String, Tempfile, IO), opts: T::Hash[T.untyped, T.untyped]).void } def initialize(input, opts = {}) @cache = T.let(T.unsafe(nil), PDF::Reader::ObjectCache) @objects = T.let(T.unsafe(nil), PDF::Reader::ObjectHash) @@ -24,7 +24,7 @@ module PDF sig { returns(Float) } def pdf_version; end - sig { params(input: T.any(String, IO), opts: T::Hash[T.untyped, T.untyped], block: T.proc.params(arg0: PDF::Reader).void).returns(T.untyped) } + sig { params(input: T.any(String, Tempfile, IO), opts: T::Hash[T.untyped, T.untyped], block: T.proc.params(arg0: PDF::Reader).void).returns(T.untyped) } def self.open(input, opts = {}, &block); end sig { returns(T::Array[PDF::Reader::Page]) } @@ -93,11 +93,11 @@ module PDF sig { returns(Integer) } attr_reader :pos - sig { params(io: T.any(StringIO, IO), opts: T::Hash[Symbol, T.untyped]).void } + sig { params(io: T.any(StringIO, Tempfile, IO), opts: T::Hash[Symbol, T.untyped]).void } def initialize(io, opts = {}) @pos = T.let(T.unsafe(nil), Integer) @tokens = T.let(T.unsafe(nil), T::Array[T.any(String, PDF::Reader::Reference)]) - @io = T.let(T.unsafe(nil), T.any(StringIO, IO)) + @io = T.let(T.unsafe(nil), T.any(StringIO, Tempfile, IO)) @in_content_stream = T.let(T.unsafe(nil), T::Boolean) end @@ -657,9 +657,9 @@ module PDF )) } attr_reader :sec_handler - sig { params(input: T.any(IO, StringIO, String), opts: T::Hash[Symbol, T.untyped]).void } + sig { params(input: T.any(IO, Tempfile, StringIO, String), opts: T::Hash[Symbol, T.untyped]).void } def initialize(input, opts = {}) - @io = T.let(T.unsafe(nil), T.any(IO, StringIO)) + @io = T.let(T.unsafe(nil), T.any(IO, Tempfile, StringIO)) @xref = T.let(T.unsafe(nil), PDF::Reader::XRef) @pdf_version = T.let(T.unsafe(nil), Float) @trailer = T.let(T.unsafe(nil), T::Hash[Symbol, T.untyped]) @@ -800,7 +800,7 @@ module PDF sig { returns(Float) } def read_version; end - sig { params(input: T.any(IO, StringIO, String)).returns(T.any(IO, StringIO)) } + sig { params(input: T.any(IO, Tempfile, StringIO, String)).returns(T.any(IO, Tempfile, StringIO)) } def extract_io_from(input); end sig { params(input: String).returns(String) } @@ -1842,9 +1842,9 @@ module PDF sig { returns(T::Hash[Symbol, T.untyped]) } attr_reader :trailer - sig { params(io: T.any(IO, StringIO)).void } + sig { params(io: T.any(IO, Tempfile, StringIO)).void } def initialize(io) - @io = T.let(T.unsafe(nil), T.any(IO, StringIO)) + @io = T.let(T.unsafe(nil), T.any(IO, Tempfile, StringIO)) @junk_offset = T.let(T.unsafe(nil), Integer) @xref = T.let(T.unsafe(nil), T::Hash[Integer, T::Hash[Integer, Integer]]) @trailer = T.let(T.unsafe(nil), T::Hash[Symbol, T.untyped]) diff --git a/spec/object_hash_spec.rb b/spec/object_hash_spec.rb index 1fd1b3bf..8c073c51 100644 --- a/spec/object_hash_spec.rb +++ b/spec/object_hash_spec.rb @@ -20,8 +20,27 @@ expect(h.map { |ref, obj| obj.class }.size).to eql(57) end + it "correctly loads a PDF from a string containing a file path" do + filename = pdf_spec_file("cairo-unicode") + h = PDF::Reader::ObjectHash.new(filename) + + expect(h.map { |ref, obj| obj.class }.size).to eql(57) + end + + # This means PDF::Reader.open(URI.open("https://example.com/foo.pdf")) will work + it "correctly loads a PDF via a Tempfile object" do + filename = pdf_spec_file("cairo-unicode") + + Tempfile.open('foo.pdf') do |tempfile| + tempfile.write binread(filename) + tempfile.rewind + h = PDF::Reader::ObjectHash.new(tempfile) + + expect(h.map { |ref, obj| obj.class }.size).to eql(57) + end + end + it "raises an ArgumentError if passed a non filename and non IO" do - pdf_spec_file("cairo-unicode") expect {PDF::Reader::ObjectHash.new(10)}.to raise_error(ArgumentError) end end