<?xml version="1.0" encoding="UTF-8"?>
<commit>
  <added type="array">
    <added>
      <filename>specs/data/form_xobject.pdf</filename>
    </added>
  </added>
  <modified type="array">
    <modified>
      <diff>@@ -1,3 +1,8 @@
+v0.7.7 (XXX)
+- Trigger callbacks contained in Form XObjects when we encounter them in a
+  content stream
+- Fix inheritance of page resources to comply with section 3.6.2
+
 v0.7.6 (28th August 2009)
 - Various bug fixes that increase the files we can successfully parse
   - Treat float and integer tokens differently (thanks Neil)</diff>
      <filename>CHANGELOG</filename>
    </modified>
    <modified>
      <diff>@@ -147,18 +147,14 @@ class PDF::Reader
   # - metadata
   # - xml_metadata
   # - page_count
+  # - begin_form_xobject
+  # - end_form_xobject
   #
   # == Resource Callbacks
   #
-  # Each page and page_container can contain a range of resources required for the page,
+  # Each page can contain (or inherit) a range of resources required for the page,
   # including things like fonts and images. The following callbacks may appear
-  # after begin_page_container and begin_page if the relevant resources exist
-  # on a page:
-  #
-  # In most cases, these callbacks associate a name with each resource, allowing it
-  # to be referred to by name in the page content. For example, an XObject can hold an image.
-  # If it gets mapped to the name &quot;IM1&quot;, then it can be placed on the page using
-  # invoke_xobject &quot;IM1&quot;.
+  # after begin_page if the relevant resources exist on a page:
   #
   # - resource_procset
   # - resource_xobject
@@ -166,6 +162,12 @@ class PDF::Reader
   # - resource_colorspace
   # - resource_pattern
   # - resource_font
+  #
+  # In most cases, these callbacks associate a name with each resource, allowing it
+  # to be referred to by name in the page content. For example, an XObject can hold an image.
+  # If it gets mapped to the name &quot;IM1&quot;, then it can be placed on the page using
+  # invoke_xobject &quot;IM1&quot;.
+  #
   class Content
     OPERATORS = {
       'b'   =&gt; :close_fill_stroke,
@@ -284,22 +286,19 @@ class PDF::Reader
     # its content
     def walk_pages (page)
 
-      if page[:Resources]
-        res = page[:Resources]
-        page.delete(:Resources)
-      end
-
       # extract page content
       if page[:Type] == :Pages
         callback(:begin_page_container, [page])
-        walk_resources(@xref.object(res)) if res
+        res = @xref.object(page[:Resources])
+        resources.push res if res
         @xref.object(page[:Kids]).each {|child| walk_pages(@xref.object(child))}
+        resources.pop if res
         callback(:end_page_container)
       elsif page[:Type] == :Page
         callback(:begin_page, [page])
-        walk_resources(@xref.object(res)) if res
-        @page = page
-        @params = []
+        res = @xref.object(page[:Resources])
+        resources.push res if res
+        walk_resources(current_resources)
 
         if @xref.object(page[:Contents]).kind_of?(Array)
           contents = @xref.object(page[:Contents])
@@ -312,10 +311,38 @@ class PDF::Reader
           content_stream(obj)
         end if page.has_key?(:Contents) and page[:Contents]
 
+        resources.pop if res
         callback(:end_page)
       end
     end
     ################################################################################
+    # Retreive the XObject for the supplied label and if it's a Form, walk it
+    # like a regular page content stream.
+    #
+    def walk_xobject_form(label)
+      xobjects = current_resources[:XObject] || {}
+      xobject  = @xref.object(xobjects[label])
+
+      if xobject &amp;&amp; xobject.hash[:Subtype] == :Form
+        callback(:begin_form_xobject)
+        resources = @xref.object(xobject.hash[:Resources])
+        walk_resources(resources) if resources
+        content_stream(xobject.to_s)
+        callback(:end_form_xobject)
+      end
+    end
+
+    ################################################################################
+    # Return a merged hash of all resources that are current. Pages, page and xobject
+    #
+    def current_resources
+      hash = {}
+      resources.each do |res|
+        hash.merge!(res)
+      end
+      hash
+    end
+    ################################################################################
     # Reads a PDF content stream and calls all the appropriate callback methods for the operators
     # it contains
     def content_stream (instructions)
@@ -341,8 +368,16 @@ class PDF::Reader
             # read the raw image data from the buffer without tokenising
             @params &lt;&lt; @buffer.read_until(&quot;EI&quot;)
           end
+
           callback(OPERATORS[token], @params)
-          @params.clear
+
+          if OPERATORS[token] == :invoke_xobject
+            xobject_label = @params.first
+            @params.clear
+            walk_xobject_form(xobject_label)
+          else
+            @params.clear
+          end
         else
           @params &lt;&lt; token
         end
@@ -352,6 +387,8 @@ class PDF::Reader
     end
     ################################################################################
     def walk_resources(resources)
+      return unless resources.respond_to?(:[])
+
       resources = resolve_references(resources)
 
       # extract any procset information
@@ -446,6 +483,9 @@ class PDF::Reader
         obj
       end
     end
+    def resources
+      @resources ||= []
+    end
   end
   ################################################################################
 end</diff>
      <filename>lib/pdf/reader/content.rb</filename>
    </modified>
    <modified>
      <diff>@@ -174,4 +174,14 @@ context &quot;PDF::Reader&quot; do
     receiver.content.size.should eql(1)
     receiver.content[0][0,7].should eql(&quot;WORKING&quot;)
   end
+
+  specify &quot;should correctly process a PDF that uses Form XObjects to repeat content&quot; do
+    receiver = PageTextReceiver.new
+    PDF::Reader.file(File.dirname(__FILE__) + &quot;/data/form_xobject.pdf&quot;, receiver)
+
+    # confirm the text appears on the correct pages
+    receiver.content.size.should eql(2)
+    receiver.content[0].should eql(&quot;James Healy&quot;)
+    receiver.content[1].should eql(&quot;James Healy&quot;)
+  end
 end</diff>
      <filename>specs/meta_spec.rb</filename>
    </modified>
  </modified>
  <removed type="array"/>
  <parents type="array">
    <parent>
      <id>81f2812962b0e8078e867d3948cf10c27bf88474</id>
    </parent>
  </parents>
  <author>
    <name>James Healy</name>
    <email>jimmy@deefa.com</email>
  </author>
  <url>http://github.com/yob/pdf-reader/commit/5d4ed95b36470472dbb90959fc0b14cc316a2891</url>
  <id>5d4ed95b36470472dbb90959fc0b14cc316a2891</id>
  <committed-date>2009-09-11T00:45:42-07:00</committed-date>
  <authored-date>2009-09-11T00:40:33-07:00</authored-date>
  <message>trigger Form XObject callbacks inline
- Form XObjects are used to place repeated elements in multiple paces in
  a document. The repeated content is only stored once, so there's a
  space saving
- They're also used to draw Bates numbers on a page
- when a Form XObject is encountered in a content stream, trigger all
  the callbacks it contains, wrapped in :start_form_xobject and
  :end_form_object callbacks.</message>
  <tree>38b37240526446c1ee28f0d54cd89130b6a2b34b</tree>
  <committer>
    <name>James Healy</name>
    <email>jimmy@deefa.com</email>
  </committer>
</commit>
