docusealco/docuseal · error · Pdfium::PdfiumError

Failed to load document from IO, pointer is NULL.

Error message

Failed to load document from IO, pointer is NULL.

What it means

Raised by Pdfium::Document.open_io when FPDF_LoadCustomDocument returns NULL. open_io first delegates to open_file when the IO responds to path (after flush); otherwise it installs an FPDF_FILEACCESS read callback backed by io.seek/io.read and declares io.size as m_FileLen. This failure means the callback-based reads came up short (the callback returns 0 on short reads), the declared length was wrong, or the stream is corrupt or encrypted. check_last_error runs first and raises the error-code variant (PasswordError for FPDF_ERR_PASSWORD) when a code was recorded.

Source

Thrown at lib/pdfium.rb:674

        bytes = io.read(size).to_s

        out.put_bytes(0, bytes)

        bytes.bytesize == size ? 1 : 0
      end

      file_access = Pdfium::FPDF_FILEACCESS.new
      file_access[:m_FileLen] = io.size
      file_access[:m_GetBlock] = get_block
      file_access[:m_Param] = FFI::Pointer::NULL

      doc_ptr = Pdfium.FPDF_LoadCustomDocument(file_access, password)

      if doc_ptr.null?
        Pdfium.check_last_error('Failed to load document from IO')

        raise PdfiumError, 'Failed to load document from IO, pointer is NULL.'
      end

      doc = new(doc_ptr, [file_access, get_block, io])

      return doc unless block_given?

      begin
        yield doc
      ensure
        doc.close
      end
    end

    def closed?
      @closed
    end

    def ensure_not_closed!

View on GitHub (pinned to 004a22c1c8)

Solutions

  1. Call io.rewind and io.binmode, and make sure the IO has not been consumed, before loading
  2. If the stream is not reliably seekable, read it fully (io.read) and use open_bytes instead
  3. Confirm io.size matches the actual content length (callback short-reads fail the whole load)
  4. Rescue Pdfium::PasswordError and retry with the password for encrypted streams

Example fix

# before
doc = Pdfium::Document.open_io(socket_io)

# after - buffer unreliable streams first
data = io.read
doc = Pdfium::Document.open_bytes(data)
Defensive patterns

Strategy: validation

Validate before calling

io.binmode
io.rewind if io.respond_to?(:rewind)
raise ArgumentError, 'IO must expose #size' unless io.respond_to?(:size) && io.size.positive?
# if the stream may be short/non-seekable, buffer it instead:
# Pdfium::Document.open_bytes(io.read)

Try / catch

begin
  Pdfium::Document.open_io(io, password)
rescue Pdfium::PasswordError
  retry_with_password
rescue Pdfium::PdfiumError => e
  data = io.rewind && io.read # salvage: retry once from buffered bytes
  data ? Pdfium::Document.open_bytes(data, password) : raise
end

Prevention

When it happens

Trigger: An IO whose read(size) returns fewer bytes than requested (socket closed mid-read, wrapper stream exhausted); io.size not matching the true byte length; a non-seekable pipe; a stream another layer already consumed; an encrypted stream without a password.

Common situations: Streaming HTTP downloads straight into PDFium; reading from decoding wrapper IOs; StringIO fixtures with wrong size; reusing an IO after an earlier parse consumed it.

Related errors


AI-assisted analysis of docusealco/docuseal@004a22c1c8 (2026-08-21). Data as JSON: /api/errors/b9f46871b5e5a0a9. Report an issue: GitHub.