Fill m_ContentStream field in CPDF_PageObject.

From the comment of CPDF_PageObject::GetContentStream():

"""
Get what content stream the object was parsed from in its page.

This number is the index of the content stream in the "Contents" array,
or 0 if there is a single content stream. If the object is newly
created, -1 is returned.

If the object is spread among more than one content stream, this is
the index of the last one.
"""

Bug: pdfium:1051
Change-Id: I9f7804af4f263dda0422e9542e025e3320ff7c31
Reviewed-on: https://pdfium-review.googlesource.com/34250
Commit-Queue: Henrique Nakashima <hnakashima@chromium.org>
Reviewed-by: dsinclair <dsinclair@chromium.org>
diff --git a/core/fpdfapi/page/cpdf_contentparser.cpp b/core/fpdfapi/page/cpdf_contentparser.cpp
index 77cfade..2bb376e 100644
--- a/core/fpdfapi/page/cpdf_contentparser.cpp
+++ b/core/fpdfapi/page/cpdf_contentparser.cpp
@@ -164,11 +164,13 @@
 
   FX_SAFE_UINT32 safeSize = 0;
   for (const auto& stream : m_StreamArray) {
+    m_StreamSegmentOffsets.push_back(safeSize.ValueOrDie());
+
     safeSize += stream->GetSize();
     safeSize += 1;
+    if (!safeSize.IsValid())
+      return Stage::kComplete;
   }
-  if (!safeSize.IsValid())
-    return Stage::kComplete;
 
   m_Size = safeSize.ValueOrDie();
   m_pData.Reset(
@@ -198,9 +200,12 @@
   if (m_CurrentOffset >= m_Size)
     return Stage::kCheckClip;
 
-  m_CurrentOffset +=
-      m_pParser->Parse(m_pData.Get() + m_CurrentOffset,
-                       m_Size - m_CurrentOffset, PARSE_STEP_LIMIT);
+  if (m_StreamSegmentOffsets.empty())
+    m_StreamSegmentOffsets.push_back(0);
+
+  m_CurrentOffset += m_pParser->Parse(m_pData.Get() + m_CurrentOffset,
+                                      m_Size - m_CurrentOffset,
+                                      PARSE_STEP_LIMIT, m_StreamSegmentOffsets);
   return Stage::kParse;
 }
 
diff --git a/core/fpdfapi/page/cpdf_contentparser.h b/core/fpdfapi/page/cpdf_contentparser.h
index f9b491d..b5db5d5 100644
--- a/core/fpdfapi/page/cpdf_contentparser.h
+++ b/core/fpdfapi/page/cpdf_contentparser.h
@@ -58,6 +58,7 @@
   UnownedPtr<CPDF_Type3Char> m_pType3Char;  // Only used when parsing forms.
   RetainPtr<CPDF_StreamAcc> m_pSingleStream;
   std::vector<RetainPtr<CPDF_StreamAcc>> m_StreamArray;
+  std::vector<uint32_t> m_StreamSegmentOffsets;
   MaybeOwned<uint8_t, FxFreeDeleter> m_pData;
   uint32_t m_nStreams = 0;
   uint32_t m_Size = 0;
diff --git a/core/fpdfapi/page/cpdf_formobject.cpp b/core/fpdfapi/page/cpdf_formobject.cpp
index eca92ca..22ac0d3 100644
--- a/core/fpdfapi/page/cpdf_formobject.cpp
+++ b/core/fpdfapi/page/cpdf_formobject.cpp
@@ -10,9 +10,12 @@
 
 #include "core/fpdfapi/page/cpdf_form.h"
 
-CPDF_FormObject::CPDF_FormObject(std::unique_ptr<CPDF_Form> pForm,
+CPDF_FormObject::CPDF_FormObject(int32_t content_stream,
+                                 std::unique_ptr<CPDF_Form> pForm,
                                  const CFX_Matrix& matrix)
-    : m_pForm(std::move(pForm)), m_FormMatrix(matrix) {}
+    : CPDF_PageObject(content_stream),
+      m_pForm(std::move(pForm)),
+      m_FormMatrix(matrix) {}
 
 CPDF_FormObject::~CPDF_FormObject() {}
 
diff --git a/core/fpdfapi/page/cpdf_formobject.h b/core/fpdfapi/page/cpdf_formobject.h
index c723cc0..b229dce 100644
--- a/core/fpdfapi/page/cpdf_formobject.h
+++ b/core/fpdfapi/page/cpdf_formobject.h
@@ -16,7 +16,9 @@
 
 class CPDF_FormObject : public CPDF_PageObject {
  public:
-  CPDF_FormObject(std::unique_ptr<CPDF_Form> pForm, const CFX_Matrix& matrix);
+  CPDF_FormObject(int32_t content_stream,
+                  std::unique_ptr<CPDF_Form> pForm,
+                  const CFX_Matrix& matrix);
   ~CPDF_FormObject() override;
 
   // CPDF_PageObject:
diff --git a/core/fpdfapi/page/cpdf_imageobject.cpp b/core/fpdfapi/page/cpdf_imageobject.cpp
index 3b5a740..516a6e8 100644
--- a/core/fpdfapi/page/cpdf_imageobject.cpp
+++ b/core/fpdfapi/page/cpdf_imageobject.cpp
@@ -12,7 +12,10 @@
 #include "core/fpdfapi/page/cpdf_image.h"
 #include "core/fpdfapi/parser/cpdf_document.h"
 
-CPDF_ImageObject::CPDF_ImageObject() {}
+CPDF_ImageObject::CPDF_ImageObject(int32_t content_stream)
+    : CPDF_PageObject(content_stream) {}
+
+CPDF_ImageObject::CPDF_ImageObject() : CPDF_ImageObject(kNoContentStream) {}
 
 CPDF_ImageObject::~CPDF_ImageObject() {
   MaybePurgeCache();
diff --git a/core/fpdfapi/page/cpdf_imageobject.h b/core/fpdfapi/page/cpdf_imageobject.h
index 16a506e..d54ef8d 100644
--- a/core/fpdfapi/page/cpdf_imageobject.h
+++ b/core/fpdfapi/page/cpdf_imageobject.h
@@ -16,6 +16,7 @@
 
 class CPDF_ImageObject : public CPDF_PageObject {
  public:
+  explicit CPDF_ImageObject(int32_t content_stream);
   CPDF_ImageObject();
   ~CPDF_ImageObject() override;
 
diff --git a/core/fpdfapi/page/cpdf_pageobject.cpp b/core/fpdfapi/page/cpdf_pageobject.cpp
index 8bb5bf5..604309f 100644
--- a/core/fpdfapi/page/cpdf_pageobject.cpp
+++ b/core/fpdfapi/page/cpdf_pageobject.cpp
@@ -6,7 +6,12 @@
 
 #include "core/fpdfapi/page/cpdf_pageobject.h"
 
-CPDF_PageObject::CPDF_PageObject() : m_bDirty(false) {}
+constexpr int32_t CPDF_PageObject::kNoContentStream;
+
+CPDF_PageObject::CPDF_PageObject(int32_t content_stream)
+    : m_bDirty(false), m_ContentStream(content_stream) {}
+
+CPDF_PageObject::CPDF_PageObject() : CPDF_PageObject(kNoContentStream) {}
 
 CPDF_PageObject::~CPDF_PageObject() {}
 
diff --git a/core/fpdfapi/page/cpdf_pageobject.h b/core/fpdfapi/page/cpdf_pageobject.h
index d23cd97..39e7629 100644
--- a/core/fpdfapi/page/cpdf_pageobject.h
+++ b/core/fpdfapi/page/cpdf_pageobject.h
@@ -28,6 +28,9 @@
     FORM,
   };
 
+  static constexpr int32_t kNoContentStream = -1;
+
+  explicit CPDF_PageObject(int32_t content_stream);
   CPDF_PageObject();
   ~CPDF_PageObject() override;
 
@@ -59,6 +62,14 @@
   }
   FX_RECT GetBBox(const CFX_Matrix* pMatrix) const;
 
+  // Get what content stream the object was parsed from in its page. This number
+  // is the index of the content stream in the "Contents" array, or 0 if there
+  // is a single content stream. If the object is newly created,
+  // kNoContentStream is returned.
+  // If the object is spread among more than one content stream, this is the
+  // index of the last stream.
+  int32_t GetContentStream() const { return m_ContentStream; }
+
   float m_Left;
   float m_Right;
   float m_Top;
@@ -73,6 +84,7 @@
   void operator=(const CPDF_PageObject& src) = delete;
 
   bool m_bDirty;
+  int32_t m_ContentStream;
 };
 
 #endif  // CORE_FPDFAPI_PAGE_CPDF_PAGEOBJECT_H_
diff --git a/core/fpdfapi/page/cpdf_pathobject.cpp b/core/fpdfapi/page/cpdf_pathobject.cpp
index d8c2cb8..0882dc1 100644
--- a/core/fpdfapi/page/cpdf_pathobject.cpp
+++ b/core/fpdfapi/page/cpdf_pathobject.cpp
@@ -6,7 +6,10 @@
 
 #include "core/fpdfapi/page/cpdf_pathobject.h"
 
-CPDF_PathObject::CPDF_PathObject() : m_FillType(0), m_bStroke(false) {}
+CPDF_PathObject::CPDF_PathObject(int32_t content_stream)
+    : CPDF_PageObject(content_stream), m_FillType(0), m_bStroke(false) {}
+
+CPDF_PathObject::CPDF_PathObject() : CPDF_PathObject(kNoContentStream) {}
 
 CPDF_PathObject::~CPDF_PathObject() {}
 
diff --git a/core/fpdfapi/page/cpdf_pathobject.h b/core/fpdfapi/page/cpdf_pathobject.h
index 58499b5..5155c40 100644
--- a/core/fpdfapi/page/cpdf_pathobject.h
+++ b/core/fpdfapi/page/cpdf_pathobject.h
@@ -14,6 +14,7 @@
 
 class CPDF_PathObject : public CPDF_PageObject {
  public:
+  explicit CPDF_PathObject(int32_t content_stream);
   CPDF_PathObject();
   ~CPDF_PathObject() override;
 
diff --git a/core/fpdfapi/page/cpdf_shadingobject.cpp b/core/fpdfapi/page/cpdf_shadingobject.cpp
index 1b16ac4..725e2e4 100644
--- a/core/fpdfapi/page/cpdf_shadingobject.cpp
+++ b/core/fpdfapi/page/cpdf_shadingobject.cpp
@@ -9,9 +9,10 @@
 #include "core/fpdfapi/page/cpdf_shadingpattern.h"
 #include "core/fpdfapi/parser/cpdf_document.h"
 
-CPDF_ShadingObject::CPDF_ShadingObject(CPDF_ShadingPattern* pattern,
+CPDF_ShadingObject::CPDF_ShadingObject(int32_t content_stream,
+                                       CPDF_ShadingPattern* pattern,
                                        const CFX_Matrix& matrix)
-    : m_pShading(pattern), m_Matrix(matrix) {}
+    : CPDF_PageObject(content_stream), m_pShading(pattern), m_Matrix(matrix) {}
 
 CPDF_ShadingObject::~CPDF_ShadingObject() {}
 
diff --git a/core/fpdfapi/page/cpdf_shadingobject.h b/core/fpdfapi/page/cpdf_shadingobject.h
index 80e062c..69b6067 100644
--- a/core/fpdfapi/page/cpdf_shadingobject.h
+++ b/core/fpdfapi/page/cpdf_shadingobject.h
@@ -15,7 +15,9 @@
 
 class CPDF_ShadingObject : public CPDF_PageObject {
  public:
-  CPDF_ShadingObject(CPDF_ShadingPattern* pattern, const CFX_Matrix& matrix);
+  CPDF_ShadingObject(int32_t content_stream,
+                     CPDF_ShadingPattern* pattern,
+                     const CFX_Matrix& matrix);
   ~CPDF_ShadingObject() override;
 
   // CPDF_PageObject:
diff --git a/core/fpdfapi/page/cpdf_streamcontentparser.cpp b/core/fpdfapi/page/cpdf_streamcontentparser.cpp
index 7562fb3..0cc81f1 100644
--- a/core/fpdfapi/page/cpdf_streamcontentparser.cpp
+++ b/core/fpdfapi/page/cpdf_streamcontentparser.cpp
@@ -785,7 +785,8 @@
   CFX_Matrix matrix = m_pCurStates->m_CTM;
   matrix.Concat(m_mtContentToUser);
 
-  auto pFormObj = pdfium::MakeUnique<CPDF_FormObject>(std::move(form), matrix);
+  auto pFormObj = pdfium::MakeUnique<CPDF_FormObject>(GetCurrentStreamIndex(),
+                                                      std::move(form), matrix);
   if (!m_pObjectHolder->BackgroundAlphaNeeded() &&
       pFormObj->form()->BackgroundAlphaNeeded()) {
     m_pObjectHolder->SetBackgroundAlphaNeeded(true);
@@ -800,14 +801,16 @@
   if (!pStream)
     return nullptr;
 
-  auto pImageObj = pdfium::MakeUnique<CPDF_ImageObject>();
+  auto pImageObj =
+      pdfium::MakeUnique<CPDF_ImageObject>(GetCurrentStreamIndex());
   pImageObj->SetImage(
       pdfium::MakeRetain<CPDF_Image>(m_pDocument.Get(), std::move(pStream)));
   return AddImageObject(std::move(pImageObj));
 }
 
 CPDF_ImageObject* CPDF_StreamContentParser::AddImage(uint32_t streamObjNum) {
-  auto pImageObj = pdfium::MakeUnique<CPDF_ImageObject>();
+  auto pImageObj =
+      pdfium::MakeUnique<CPDF_ImageObject>(GetCurrentStreamIndex());
   pImageObj->SetImage(m_pDocument->LoadImageFromPageData(streamObjNum));
   return AddImageObject(std::move(pImageObj));
 }
@@ -817,7 +820,8 @@
   if (!pImage)
     return nullptr;
 
-  auto pImageObj = pdfium::MakeUnique<CPDF_ImageObject>();
+  auto pImageObj =
+      pdfium::MakeUnique<CPDF_ImageObject>(GetCurrentStreamIndex());
   pImageObj->SetImage(
       m_pDocument->GetPageData()->GetImage(pImage->GetStream()->GetObjNum()));
 
@@ -1084,7 +1088,8 @@
 
   CFX_Matrix matrix = m_pCurStates->m_CTM;
   matrix.Concat(m_mtContentToUser);
-  auto pObj = pdfium::MakeUnique<CPDF_ShadingObject>(pShading, matrix);
+  auto pObj = pdfium::MakeUnique<CPDF_ShadingObject>(GetCurrentStreamIndex(),
+                                                     pShading, matrix);
   SetGraphicStates(pObj.get(), false, false, false);
   CFX_FloatRect bbox =
       pObj->m_ClipPath.HasRef() ? pObj->m_ClipPath.GetClipBox() : m_BBox;
@@ -1219,7 +1224,7 @@
       pFont->IsType3Font() ? TextRenderingMode::MODE_FILL
                            : m_pCurStates->m_TextState.GetTextMode();
   {
-    auto pText = pdfium::MakeUnique<CPDF_TextObject>();
+    auto pText = pdfium::MakeUnique<CPDF_TextObject>(GetCurrentStreamIndex());
     m_pLastTextObject = pText.get();
     SetGraphicStates(m_pLastTextObject.Get(), true, true, true);
     if (TextRenderingModeIsStrokeMode(text_mode)) {
@@ -1258,6 +1263,12 @@
   }
 }
 
+int32_t CPDF_StreamContentParser::GetCurrentStreamIndex() {
+  auto it = std::upper_bound(m_StreamStartOffsets.begin(),
+                             m_StreamStartOffsets.end(), m_pSyntax->GetPos());
+  return (it - m_StreamStartOffsets.begin()) - 1;
+}
+
 void CPDF_StreamContentParser::Handle_ShowText() {
   ByteString str = GetString(0);
   if (str.IsEmpty()) {
@@ -1456,7 +1467,8 @@
   CFX_Matrix matrix = m_pCurStates->m_CTM;
   matrix.Concat(m_mtContentToUser);
   if (bStroke || FillType) {
-    auto pPathObj = pdfium::MakeUnique<CPDF_PathObject>();
+    auto pPathObj =
+        pdfium::MakeUnique<CPDF_PathObject>(GetCurrentStreamIndex());
     pPathObj->m_bStroke = bStroke;
     pPathObj->m_FillType = FillType;
     pPathObj->m_Path = Path;
@@ -1474,22 +1486,27 @@
   }
 }
 
-uint32_t CPDF_StreamContentParser::Parse(const uint8_t* pData,
-                                         uint32_t dwSize,
-                                         uint32_t max_cost) {
+uint32_t CPDF_StreamContentParser::Parse(
+    const uint8_t* pData,
+    uint32_t dwSize,
+    uint32_t max_cost,
+    const std::vector<uint32_t>& stream_start_offsets) {
   if (m_ParsedSet->size() > kMaxFormLevel ||
       pdfium::ContainsKey(*m_ParsedSet, pData))
     return dwSize;
 
+  m_StreamStartOffsets = stream_start_offsets;
+
   pdfium::ScopedSetInsertion<const uint8_t*> scopedInsert(m_ParsedSet.Get(),
                                                           pData);
 
-  uint32_t InitObjCount = m_pObjectHolder->GetPageObjectList()->size();
+  uint32_t init_obj_count = m_pObjectHolder->GetPageObjectList()->size();
   CPDF_StreamParser syntax(pdfium::make_span(pData, dwSize),
                            m_pDocument->GetByteStringPool());
   CPDF_StreamParserAutoClearer auto_clearer(&m_pSyntax, &syntax);
   while (1) {
-    uint32_t cost = m_pObjectHolder->GetPageObjectList()->size() - InitObjCount;
+    uint32_t cost =
+        m_pObjectHolder->GetPageObjectList()->size() - init_obj_count;
     if (max_cost && cost >= max_cost) {
       break;
     }
diff --git a/core/fpdfapi/page/cpdf_streamcontentparser.h b/core/fpdfapi/page/cpdf_streamcontentparser.h
index 438be02..adcb2a5 100644
--- a/core/fpdfapi/page/cpdf_streamcontentparser.h
+++ b/core/fpdfapi/page/cpdf_streamcontentparser.h
@@ -43,7 +43,10 @@
                            std::set<const uint8_t*>* parsedSet);
   ~CPDF_StreamContentParser();
 
-  uint32_t Parse(const uint8_t* pData, uint32_t dwSize, uint32_t max_cost);
+  uint32_t Parse(const uint8_t* pData,
+                 uint32_t dwSize,
+                 uint32_t max_cost,
+                 const std::vector<uint32_t>& stream_start_offsets);
   CPDF_PageObjectHolder* GetPageObjectHolder() const {
     return m_pObjectHolder.Get();
   }
@@ -127,6 +130,7 @@
 
   std::vector<float> GetColors() const;
   std::vector<float> GetNamedColors() const;
+  int32_t GetCurrentStreamIndex();
 
   void Handle_CloseFillStrokePath();
   void Handle_FillStrokePath();
@@ -230,6 +234,7 @@
   std::vector<std::unique_ptr<CPDF_AllStates>> m_StateStack;
   float m_Type3Data[6];
   ContentParam m_ParamBuf[kParamBufSize];
+  std::vector<uint32_t> m_StreamStartOffsets;
 };
 
 #endif  // CORE_FPDFAPI_PAGE_CPDF_STREAMCONTENTPARSER_H_
diff --git a/core/fpdfapi/page/cpdf_textobject.cpp b/core/fpdfapi/page/cpdf_textobject.cpp
index 36a4722..e678d5f 100644
--- a/core/fpdfapi/page/cpdf_textobject.cpp
+++ b/core/fpdfapi/page/cpdf_textobject.cpp
@@ -18,7 +18,10 @@
 
 CPDF_TextObjectItem::~CPDF_TextObjectItem() = default;
 
-CPDF_TextObject::CPDF_TextObject() {}
+CPDF_TextObject::CPDF_TextObject(int32_t content_stream)
+    : CPDF_PageObject(content_stream) {}
+
+CPDF_TextObject::CPDF_TextObject() : CPDF_TextObject(kNoContentStream) {}
 
 CPDF_TextObject::~CPDF_TextObject() {
   // Move m_CharCodes to a local variable so it will be captured in crash dumps,
diff --git a/core/fpdfapi/page/cpdf_textobject.h b/core/fpdfapi/page/cpdf_textobject.h
index a6fc623..d3b6dcc 100644
--- a/core/fpdfapi/page/cpdf_textobject.h
+++ b/core/fpdfapi/page/cpdf_textobject.h
@@ -25,6 +25,7 @@
 
 class CPDF_TextObject : public CPDF_PageObject {
  public:
+  explicit CPDF_TextObject(int32_t content_stream);
   CPDF_TextObject();
   ~CPDF_TextObject() override;
 
diff --git a/fpdfsdk/fpdf_edit_embeddertest.cpp b/fpdfsdk/fpdf_edit_embeddertest.cpp
index dc61c0d..3d2e090 100644
--- a/fpdfsdk/fpdf_edit_embeddertest.cpp
+++ b/fpdfsdk/fpdf_edit_embeddertest.cpp
@@ -9,6 +9,7 @@
 
 #include "core/fpdfapi/font/cpdf_font.h"
 #include "core/fpdfapi/page/cpdf_page.h"
+#include "core/fpdfapi/page/cpdf_pageobject.h"
 #include "core/fpdfapi/parser/cpdf_array.h"
 #include "core/fpdfapi/parser/cpdf_dictionary.h"
 #include "core/fpdfapi/parser/cpdf_number.h"
@@ -655,6 +656,34 @@
   CloseSavedDocument();
 }
 
+// TODO(pdfium:1051): Extend this test to remove some elements and verify
+// saving works.
+TEST_F(FPDFEditEmbeddertest, GetContentStream) {
+  // Load document with some text split across streams.
+  EXPECT_TRUE(OpenDocument("split_streams.pdf"));
+  FPDF_PAGE page = LoadPage(0);
+  ASSERT_TRUE(page);
+
+  // Content stream 0: page objects 0-14.
+  // Content stream 1: page objects 15-17.
+  // Content stream 2: page object 18.
+  ASSERT_EQ(19, FPDFPage_CountObjects(page));
+  for (int i = 0; i < 19; i++) {
+    FPDF_PAGEOBJECT page_object = FPDFPage_GetObject(page, i);
+    ASSERT_TRUE(page_object);
+    CPDF_PageObject* cpdf_page_object =
+        CPDFPageObjectFromFPDFPageObject(page_object);
+    if (i < 15)
+      EXPECT_EQ(0, cpdf_page_object->GetContentStream()) << i;
+    else if (i < 18)
+      EXPECT_EQ(1, cpdf_page_object->GetContentStream()) << i;
+    else
+      EXPECT_EQ(2, cpdf_page_object->GetContentStream()) << i;
+  }
+
+  UnloadPage(page);
+}
+
 TEST_F(FPDFEditEmbeddertest, InsertPageObjectAndSave) {
   // Load document with some text.
   EXPECT_TRUE(OpenDocument("hello_world.pdf"));
diff --git a/testing/resources/split_streams.in b/testing/resources/split_streams.in
new file mode 100644
index 0000000..b134769
--- /dev/null
+++ b/testing/resources/split_streams.in
@@ -0,0 +1,124 @@
+{{header}}
+{{object 1 0}}
+<< /Pages 2 0 R /Type /Catalog >>
+endobj
+{{object 2 0}}
+<< /Count 1 /Kids [ 3 0 R ] /MediaBox [ 0 0 200 200 ] /Type /Pages >>
+endobj
+{{object 3 0}}
+<<
+  /Contents 4 0 R
+  /Parent 2 0 R
+  /Resources <<
+    /ExtGState << /FXE1 5 0 R /FXE2 6 0 R >>
+    /Font << /F1 7 0 R /F2 8 0 R /FXF1 9 0 R /FXF2 10 0 R >>
+  >>
+  /Type /Page
+>>
+endobj
+{{object 4 0}}
+[ 12 0 R 13 0 R 14 0 R ]
+endobj
+{{object 5 0}}
+<< /BM /Normal /CA 1 /ca 1 >>
+endobj
+{{object 6 0}}
+<< /ca 0.705882 >>
+endobj
+{{object 7 0}}
+<< /BaseFont /Times-Roman /Subtype /Type1 /Type /Font >>
+endobj
+{{object 8 0}}
+<< /BaseFont /Helvetica /Subtype /Type1 /Type /Font >>
+endobj
+{{object 9 0}}
+<< /BaseFont /Courier-Bold /Subtype /Type1 /Type /Font >>
+endobj
+{{object 10 0}}
+<< /BaseFont /Times-Bold /Subtype /Type1 /Type /Font >>
+endobj
+{{object 12 0}}
+<< {{streamlen}} >>
+stream
+q
+0 0 0 RG 0 0 0 rg 1 w 0 J 0 j
+/FXE1 gs
+q 0 0 0 rg /FXE2 gs BT 1 0 0 1 120 100 Tm /FXF1 9 Tf (Test 1) Tj ET Q
+q 0 0 0.0509804 rg /FXE2 gs
+BT 0.995597 -0.341789 0.341789 0.995597 119.912 93.1642
+Tm /FXF2 9 Tf (Test 2) Tj ET Q
+q 0 0 0.101961 rg /FXE2 gs
+BT 0.872208 -0.678867 0.678867 0.872208 117.444 86.4227
+Tm /FXF1 9 Tf (Test 3) Tj ET Q
+q 0 0 0.156863 rg /FXE2 gs
+BT 0.633308 -0.969351 0.969351 0.633308 112.666 80.613
+Tm /FXF2 9 Tf (Test 4) Tj ET Q
+q 0 0 0.207843 rg /FXE2 gs
+BT 0.297167 -1.17348 1.17348 0.297167 105.943 76.5303
+Tm /FXF1 9 Tf (Test 5) Tj ET Q
+q 0 0 0.262745 rg /FXE2 gs
+BT -0.104311 -1.25884 1.25884 -0.104311 97.9138 74.8231
+Tm /FXF2 9 Tf (Test 6) Tj ET Q
+q 0 0 0.313726 rg /FXE2 gs
+BT -0.528547 -1.20496 1.20496 -0.528547 89.4291 75.9007
+Tm /FXF1 9 Tf (Test 7) Tj ET Q
+q 0 0 0.364706 rg /FXE2 gs
+BT -0.926806 -1.00678 1.00678 -0.926806 81.4639 79.8644
+Tm /FXF2 9 Tf (Test 8) Tj ET Q
+q 0 0 0.419608 rg /FXE2 gs
+BT -1.24978 -0.676346 0.676346 -1.24978 75.0044 86.4731
+Tm /FXF1 9 Tf (Test 9) Tj ET Q
+q 0 0 0.470588 rg /FXE2 gs
+BT -1.45359 -0.24256 0.24256 -1.45359 70.9283 95.1488
+Tm /FXF2 9 Tf (Test 10) Tj ET Q
+q 0 0 0.52549 rg /FXE2 gs
+BT -1.5055 0.251223 -0.251223 -1.5055 69.89 105.024
+Tm /FXF1 9 Tf (Test 11) Tj ET Q
+q 0 0 0.576471 rg /FXE2 gs
+BT -1.38864 0.751496 -0.751496 -1.38864 72.2271 115.03
+Tm /FXF2 9 Tf (Test 12) Tj ET Q
+q 0 0 0.631373 rg /FXE2 gs
+BT -1.10504 1.20039 -1.20039 -1.10504 77.8992 124.008
+Tm /FXF1 9 Tf (Test 13) Tj ET Q
+q 0 0 0.682353 rg /FXE2 gs
+BT -0.67654 1.54236 -1.54236 -0.67654 86.4692 130.847
+Tm /FXF2 9 Tf (Test 14) Tj ET Q
+q 0 0 0.733333 rg /FXE2 gs
+BT -0.143427 1.73091 -1.73091 -0.143427 97.1315 134.618
+Tm /FXF1 9 Tf (Test 15) Tj ET Q
+endstream
+endobj
+
+{{object 13 0}}
+<< {{streamlen}} >>
+stream
+q 0 0 0.788235 rg /FXE2 gs
+BT 0.43929 1.73472 -1.73472 0.43929 108.786 134.694
+Tm /FXF2 9 Tf (Test 16) Tj ET Q
+q 0 0 0.839216 rg /FXE2 gs
+BT 1.00754 1.54215 -1.54215 1.00754 120.151 130.843
+Tm /FXF1 9 Tf (Test 17) Tj ET Q
+q 0 0 0.894118 rg /FXE2 gs
+BT 1.49521 1.16377 -1.16377 1.49521 129.904 123.275
+Tm /FXF2 9 Tf (Test 18) Tj ET Q
+endstream
+endobj
+
+{{object 14 0}}
+<< {{streamlen}} >>
+stream
+q 0 0 0.945098 rg /FXE2 gs
+BT 1.84185 0.632309 -0.632309 1.84185 136.837 112.646
+Tm /FXF1 9 Tf (Test 19) Tj ET Q
+Q
+endstream
+endobj
+
+{{xref}}
+trailer <<
+  /Root 1 0 R
+  /Size 15
+  /ID [<f341ae654a77acd5065a7645e596e6e6><bc37298a3f87f479229bce997ca791f7>]
+>>
+{{startxref}}
+%%EOF
diff --git a/testing/resources/split_streams.pdf b/testing/resources/split_streams.pdf
new file mode 100644
index 0000000..d1768ca
--- /dev/null
+++ b/testing/resources/split_streams.pdf
@@ -0,0 +1,142 @@
+%PDF-1.7
+% ò¤ô
+1 0 obj
+<< /Pages 2 0 R /Type /Catalog >>
+endobj
+2 0 obj
+<< /Count 1 /Kids [ 3 0 R ] /MediaBox [ 0 0 200 200 ] /Type /Pages >>
+endobj
+3 0 obj
+<<
+  /Contents 4 0 R
+  /Parent 2 0 R
+  /Resources <<
+    /ExtGState << /FXE1 5 0 R /FXE2 6 0 R >>
+    /Font << /F1 7 0 R /F2 8 0 R /FXF1 9 0 R /FXF2 10 0 R >>
+  >>
+  /Type /Page
+>>
+endobj
+4 0 obj
+[ 12 0 R 13 0 R 14 0 R ]
+endobj
+5 0 obj
+<< /BM /Normal /CA 1 /ca 1 >>
+endobj
+6 0 obj
+<< /ca 0.705882 >>
+endobj
+7 0 obj
+<< /BaseFont /Times-Roman /Subtype /Type1 /Type /Font >>
+endobj
+8 0 obj
+<< /BaseFont /Helvetica /Subtype /Type1 /Type /Font >>
+endobj
+9 0 obj
+<< /BaseFont /Courier-Bold /Subtype /Type1 /Type /Font >>
+endobj
+10 0 obj
+<< /BaseFont /Times-Bold /Subtype /Type1 /Type /Font >>
+endobj
+12 0 obj
+<< /Length 1699 >>
+stream
+q
+0 0 0 RG 0 0 0 rg 1 w 0 J 0 j
+/FXE1 gs
+q 0 0 0 rg /FXE2 gs BT 1 0 0 1 120 100 Tm /FXF1 9 Tf (Test 1) Tj ET Q
+q 0 0 0.0509804 rg /FXE2 gs
+BT 0.995597 -0.341789 0.341789 0.995597 119.912 93.1642
+Tm /FXF2 9 Tf (Test 2) Tj ET Q
+q 0 0 0.101961 rg /FXE2 gs
+BT 0.872208 -0.678867 0.678867 0.872208 117.444 86.4227
+Tm /FXF1 9 Tf (Test 3) Tj ET Q
+q 0 0 0.156863 rg /FXE2 gs
+BT 0.633308 -0.969351 0.969351 0.633308 112.666 80.613
+Tm /FXF2 9 Tf (Test 4) Tj ET Q
+q 0 0 0.207843 rg /FXE2 gs
+BT 0.297167 -1.17348 1.17348 0.297167 105.943 76.5303
+Tm /FXF1 9 Tf (Test 5) Tj ET Q
+q 0 0 0.262745 rg /FXE2 gs
+BT -0.104311 -1.25884 1.25884 -0.104311 97.9138 74.8231
+Tm /FXF2 9 Tf (Test 6) Tj ET Q
+q 0 0 0.313726 rg /FXE2 gs
+BT -0.528547 -1.20496 1.20496 -0.528547 89.4291 75.9007
+Tm /FXF1 9 Tf (Test 7) Tj ET Q
+q 0 0 0.364706 rg /FXE2 gs
+BT -0.926806 -1.00678 1.00678 -0.926806 81.4639 79.8644
+Tm /FXF2 9 Tf (Test 8) Tj ET Q
+q 0 0 0.419608 rg /FXE2 gs
+BT -1.24978 -0.676346 0.676346 -1.24978 75.0044 86.4731
+Tm /FXF1 9 Tf (Test 9) Tj ET Q
+q 0 0 0.470588 rg /FXE2 gs
+BT -1.45359 -0.24256 0.24256 -1.45359 70.9283 95.1488
+Tm /FXF2 9 Tf (Test 10) Tj ET Q
+q 0 0 0.52549 rg /FXE2 gs
+BT -1.5055 0.251223 -0.251223 -1.5055 69.89 105.024
+Tm /FXF1 9 Tf (Test 11) Tj ET Q
+q 0 0 0.576471 rg /FXE2 gs
+BT -1.38864 0.751496 -0.751496 -1.38864 72.2271 115.03
+Tm /FXF2 9 Tf (Test 12) Tj ET Q
+q 0 0 0.631373 rg /FXE2 gs
+BT -1.10504 1.20039 -1.20039 -1.10504 77.8992 124.008
+Tm /FXF1 9 Tf (Test 13) Tj ET Q
+q 0 0 0.682353 rg /FXE2 gs
+BT -0.67654 1.54236 -1.54236 -0.67654 86.4692 130.847
+Tm /FXF2 9 Tf (Test 14) Tj ET Q
+q 0 0 0.733333 rg /FXE2 gs
+BT -0.143427 1.73091 -1.73091 -0.143427 97.1315 134.618
+Tm /FXF1 9 Tf (Test 15) Tj ET Q
+endstream
+endobj
+
+13 0 obj
+<< /Length 333 >>
+stream
+q 0 0 0.788235 rg /FXE2 gs
+BT 0.43929 1.73472 -1.73472 0.43929 108.786 134.694
+Tm /FXF2 9 Tf (Test 16) Tj ET Q
+q 0 0 0.839216 rg /FXE2 gs
+BT 1.00754 1.54215 -1.54215 1.00754 120.151 130.843
+Tm /FXF1 9 Tf (Test 17) Tj ET Q
+q 0 0 0.894118 rg /FXE2 gs
+BT 1.49521 1.16377 -1.16377 1.49521 129.904 123.275
+Tm /FXF2 9 Tf (Test 18) Tj ET Q
+endstream
+endobj
+
+14 0 obj
+<< /Length 115 >>
+stream
+q 0 0 0.945098 rg /FXE2 gs
+BT 1.84185 0.632309 -0.632309 1.84185 136.837 112.646
+Tm /FXF1 9 Tf (Test 19) Tj ET Q
+Q
+endstream
+endobj
+
+xref
+0 15
+0000000000 65535 f 
+0000000015 00000 n 
+0000000064 00000 n 
+0000000149 00000 n 
+0000000345 00000 n 
+0000000385 00000 n 
+0000000430 00000 n 
+0000000464 00000 n 
+0000000536 00000 n 
+0000000606 00000 n 
+0000000679 00000 n 
+0000000000 65535 f 
+0000000751 00000 n 
+0000002503 00000 n 
+0000002888 00000 n 
+trailer <<
+  /Root 1 0 R
+  /Size 15
+  /ID [<f341ae654a77acd5065a7645e596e6e6><bc37298a3f87f479229bce997ca791f7>]
+>>
+startxref
+3055
+%%EOF