Certain PDFs as Template: NoMethodError (undefined method `[]' for nil:NilClass) #386

Closed
jaybrueder opened this Issue Aug 11, 2012 · 5 comments

Comments

Projects
None yet
6 participants
@jaybrueder

Hi!

I'm encountering a

NoMethodError (undefined method `[]' for nil:NilClass)

Exception with certain PDFs that I want to use as a template.

Another problem is, that a begin...rescue wont rescue the exception.
It still crashes the app:

begin
    start_new_page(:template => "#{Rails.root}/public#{upload.document.url}")
rescue
    start_new_page
end

I'm using the Master-Branch of the gem.

gem 'prawn', :git => "git://github.com/prawnpdf/prawn.git", :branch => "master"

Does anyone know what causes this error with some PDFs or at least why it won't be rescued?

@bradediger

This comment has been minimized.

Show comment Hide comment
@bradediger

bradediger Aug 12, 2012

Member

Can you provide either a PDF that triggers the error, or a stacktrace of the error itself? Otherwise we're just guessing as to what happened.

As for why the rescue doesn't seem to work, my guess is that by the time you hit the NameError, the damage is already done and you can't necessarily just keep on using the document.

Member

bradediger commented Aug 12, 2012

Can you provide either a PDF that triggers the error, or a stacktrace of the error itself? Otherwise we're just guessing as to what happened.

As for why the rescue doesn't seem to work, my guess is that by the time you hit the NameError, the damage is already done and you can't necessarily just keep on using the document.

@jaybrueder

This comment has been minimized.

Show comment Hide comment
@jaybrueder

jaybrueder Aug 13, 2012

I have put an example pdf here: http://juergenbrueder.com/pdfs/vorschau.pdf

The stack trace when rendering a PDF with the above PDF as template is included below.
It is not very informative. All I can say is that base_recipe_export.rb:124 is where the

start_new_page(:template => "#{Rails.root}/public#{upload.document.url}")

is located.

NoMethodError (undefined method []' for nil:NilClass): app/pdfs/base_recipe_export.rb:124:inrescue in block in recipe_pdf'
app/pdfs/base_recipe_export.rb:120:in block in recipe_pdf' app/pdfs/base_recipe_export.rb:117:ineach'
app/pdfs/base_recipe_export.rb:117:in recipe_pdf' app/pdfs/base_recipe_export.rb:66:inblock (2 levels) in render_recipes'
app/pdfs/base_recipe_export.rb:64:in block in render_recipes' app/pdfs/base_recipe_export.rb:56:ineach'
app/pdfs/base_recipe_export.rb:56:in render_recipes' app/pdfs/base_recipe_export.rb:50:inrender_all_others'
app/pdfs/recipes_export_pdf.rb:22:in initialize' app/controllers/recipes_controller.rb:32:innew'
app/controllers/recipes_controller.rb:32:in block (2 levels) in pdf_export' app/controllers/recipes_controller.rb:30:inpdf_export'
config/initializers/quiet_assets.rb:7:in `call_with_quiet_assets'

I have put an example pdf here: http://juergenbrueder.com/pdfs/vorschau.pdf

The stack trace when rendering a PDF with the above PDF as template is included below.
It is not very informative. All I can say is that base_recipe_export.rb:124 is where the

start_new_page(:template => "#{Rails.root}/public#{upload.document.url}")

is located.

NoMethodError (undefined method []' for nil:NilClass): app/pdfs/base_recipe_export.rb:124:inrescue in block in recipe_pdf'
app/pdfs/base_recipe_export.rb:120:in block in recipe_pdf' app/pdfs/base_recipe_export.rb:117:ineach'
app/pdfs/base_recipe_export.rb:117:in recipe_pdf' app/pdfs/base_recipe_export.rb:66:inblock (2 levels) in render_recipes'
app/pdfs/base_recipe_export.rb:64:in block in render_recipes' app/pdfs/base_recipe_export.rb:56:ineach'
app/pdfs/base_recipe_export.rb:56:in render_recipes' app/pdfs/base_recipe_export.rb:50:inrender_all_others'
app/pdfs/recipes_export_pdf.rb:22:in initialize' app/controllers/recipes_controller.rb:32:innew'
app/controllers/recipes_controller.rb:32:in block (2 levels) in pdf_export' app/controllers/recipes_controller.rb:30:inpdf_export'
config/initializers/quiet_assets.rb:7:in `call_with_quiet_assets'

@TylerRick

This comment has been minimized.

Show comment Hide comment
@TylerRick

TylerRick Nov 14, 2012

It looks like your backtrace is only showing frames from your application files and therefore omits any lines from gem/framework source code. Those omitted lines would be needed to debug this.

If the error message shows up in your browser, there should be 3 links "Application Trace | Framework Trace | Full Trace" and you can see the full trace by clicking Full Trace.

If the backtrace is only showing in your log file, you might try adding this line to config/initializers/backtrace_silencers.rb:

Rails.backtrace_cleaner.remove_silencers!

It looks like your backtrace is only showing frames from your application files and therefore omits any lines from gem/framework source code. Those omitted lines would be needed to debug this.

If the error message shows up in your browser, there should be 3 links "Application Trace | Framework Trace | Full Trace" and you can see the full trace by clicking Full Trace.

If the backtrace is only showing in your log file, you might try adding this line to config/initializers/backtrace_silencers.rb:

Rails.backtrace_cleaner.remove_silencers!
@yob

This comment has been minimized.

Show comment Hide comment
@yob

yob Dec 8, 2012

Member

I've confirmed this is a bug, here's the stack trace using the provided template PDF

⚡ ruby -Ilib manual/templates/full_template.rb
    /home/jh/git/prawn-rw/lib/prawn/core/page.rb:38:in `layout': undefined method `[]' for nil:NilClass (NoMethodError)
    from /home/jh/git/prawn-rw/lib/prawn/document.rb:253:in `start_new_page'
    from manual/templates/full_template.rb:19:in `block in <main>'
    from /home/jh/git/prawn-rw/lib/prawn/document.rb:215:in `instance_eval'
    from /home/jh/git/prawn-rw/lib/prawn/document.rb:215:in `initialize'
    from /home/jh/git/prawn-rw/lib/prawn/document.rb:122:in `new'
    from /home/jh/git/prawn-rw/lib/prawn/document.rb:122:in `generate'
    from manual/templates/full_template.rb:16:in `<main>'

The template PDF defines it's MediaBox in the /Pages tree above the /Page, not on the /Page itself. it's perfectly legal for the PDF to do that, so we need to account for it.

I have an idea that should help and will hopefully be able to look at it soon.

Member

yob commented Dec 8, 2012

I've confirmed this is a bug, here's the stack trace using the provided template PDF

⚡ ruby -Ilib manual/templates/full_template.rb
    /home/jh/git/prawn-rw/lib/prawn/core/page.rb:38:in `layout': undefined method `[]' for nil:NilClass (NoMethodError)
    from /home/jh/git/prawn-rw/lib/prawn/document.rb:253:in `start_new_page'
    from manual/templates/full_template.rb:19:in `block in <main>'
    from /home/jh/git/prawn-rw/lib/prawn/document.rb:215:in `instance_eval'
    from /home/jh/git/prawn-rw/lib/prawn/document.rb:215:in `initialize'
    from /home/jh/git/prawn-rw/lib/prawn/document.rb:122:in `new'
    from /home/jh/git/prawn-rw/lib/prawn/document.rb:122:in `generate'
    from manual/templates/full_template.rb:16:in `<main>'

The template PDF defines it's MediaBox in the /Pages tree above the /Page, not on the /Page itself. it's perfectly legal for the PDF to do that, so we need to account for it.

I have an idea that should help and will hopefully be able to look at it soon.

@krishicks

This comment has been minimized.

Show comment Hide comment
@krishicks

krishicks Apr 1, 2013

I've run into this bug as well. The following tests expose the bug:

Modify the existing test for a PDF without a MediaBox entry (spec/template_spec.rb:164) such that it starts a new page after creating the Document:

it "should correctly import a template file that is missing a MediaBox entry" do
  filename = "#{Prawn::DATADIR}/pdfs/page_without_mediabox.pdf"

  @pdf = Prawn::Document.new(:template => filename)
  @pdf.start_new_page
  str = @pdf.render
  str[0,4].should == "%PDF"
end

This will cause the error that the bug was opened under.

Similarly, create a new document without a MediaBox-less template, but pass the template to start_new_page:

it "should work when the template is missing a MediaBox entry" do
  filename = "#{Prawn::DATADIR}/pdfs/page_without_mediabox.pdf"

  @pdf = Prawn::Document.new skip_page_creation: true
  lambda {
    @pdf.start_new_page(template: filename)
  }.should_not raise_error
end

I've looked into this a bit but don't have a good enough understanding of the structure of PDFs to make a good solution.

Adding a breakpoint at lib/prawn/core/page.rb:91 shows the following:

p @dictionary
=> 4
p document.state.store[4].data.keys
=> [:Type, :Parent, :Resources, :Contents]

Page#layout calls #dictionary expecting an object with data that has :MediaBox as a key, but the result from #dictionary has no such key at that index.

However, that key can be found at a different index, if we dig into :Pages:

p document.state.store[2].data[:Pages].data.keys
[:Type, :Kids, :Count, :MediaBox]

The fix made in 41b56e8 by James Healy seems to do a similar kind of lookup through the parents to find the :MediaBox key, but still fails in the case of the PDF being used as a start_new_page template.

Edited to add pull request: #468

I've run into this bug as well. The following tests expose the bug:

Modify the existing test for a PDF without a MediaBox entry (spec/template_spec.rb:164) such that it starts a new page after creating the Document:

it "should correctly import a template file that is missing a MediaBox entry" do
  filename = "#{Prawn::DATADIR}/pdfs/page_without_mediabox.pdf"

  @pdf = Prawn::Document.new(:template => filename)
  @pdf.start_new_page
  str = @pdf.render
  str[0,4].should == "%PDF"
end

This will cause the error that the bug was opened under.

Similarly, create a new document without a MediaBox-less template, but pass the template to start_new_page:

it "should work when the template is missing a MediaBox entry" do
  filename = "#{Prawn::DATADIR}/pdfs/page_without_mediabox.pdf"

  @pdf = Prawn::Document.new skip_page_creation: true
  lambda {
    @pdf.start_new_page(template: filename)
  }.should_not raise_error
end

I've looked into this a bit but don't have a good enough understanding of the structure of PDFs to make a good solution.

Adding a breakpoint at lib/prawn/core/page.rb:91 shows the following:

p @dictionary
=> 4
p document.state.store[4].data.keys
=> [:Type, :Parent, :Resources, :Contents]

Page#layout calls #dictionary expecting an object with data that has :MediaBox as a key, but the result from #dictionary has no such key at that index.

However, that key can be found at a different index, if we dig into :Pages:

p document.state.store[2].data[:Pages].data.keys
[:Type, :Kids, :Count, :MediaBox]

The fix made in 41b56e8 by James Healy seems to do a similar kind of lookup through the parents to find the :MediaBox key, but still fails in the case of the PDF being used as a start_new_page template.

Edited to add pull request: #468

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment