• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright 2018 PDFium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 #include "core/fpdfapi/edit/cpdf_pagecontentmanager.h"
6 
7 #include <map>
8 #include <numeric>
9 #include <vector>
10 
11 #include "core/fpdfapi/page/cpdf_pageobject.h"
12 #include "core/fpdfapi/page/cpdf_pageobjectholder.h"
13 #include "core/fpdfapi/parser/cpdf_array.h"
14 #include "core/fpdfapi/parser/cpdf_dictionary.h"
15 #include "core/fpdfapi/parser/cpdf_document.h"
16 #include "core/fpdfapi/parser/cpdf_reference.h"
17 #include "core/fpdfapi/parser/cpdf_stream.h"
18 
CPDF_PageContentManager(const CPDF_PageObjectHolder * obj_holder)19 CPDF_PageContentManager::CPDF_PageContentManager(
20     const CPDF_PageObjectHolder* obj_holder)
21     : obj_holder_(obj_holder), doc_(obj_holder_->GetDocument()) {
22   CPDF_Dictionary* page_dict = obj_holder_->GetDict();
23   CPDF_Object* contents_obj = page_dict->GetObjectFor("Contents");
24   CPDF_Array* contents_array = ToArray(contents_obj);
25   if (contents_array) {
26     contents_array_.Reset(contents_array);
27     return;
28   }
29 
30   CPDF_Reference* contents_reference = ToReference(contents_obj);
31   if (contents_reference) {
32     CPDF_Object* indirect_obj = contents_reference->GetDirect();
33     if (!indirect_obj)
34       return;
35 
36     contents_array = indirect_obj->AsArray();
37     if (contents_array)
38       contents_array_.Reset(contents_array);
39     else if (indirect_obj->IsStream())
40       contents_stream_.Reset(indirect_obj->AsStream());
41   }
42 }
43 
44 CPDF_PageContentManager::~CPDF_PageContentManager() = default;
45 
GetStreamByIndex(size_t stream_index)46 CPDF_Stream* CPDF_PageContentManager::GetStreamByIndex(size_t stream_index) {
47   if (contents_stream_)
48     return stream_index == 0 ? contents_stream_.Get() : nullptr;
49 
50   if (contents_array_) {
51     CPDF_Reference* stream_reference =
52         ToReference(contents_array_->GetObjectAt(stream_index));
53     if (!stream_reference)
54       return nullptr;
55 
56     return stream_reference->GetDirect()->AsStream();
57   }
58 
59   return nullptr;
60 }
61 
AddStream(std::ostringstream * buf)62 size_t CPDF_PageContentManager::AddStream(std::ostringstream* buf) {
63   CPDF_Stream* new_stream = doc_->NewIndirect<CPDF_Stream>();
64   new_stream->SetDataFromStringstream(buf);
65 
66   // If there is one Content stream (not in an array), now there will be two, so
67   // create an array with the old and the new one. The new one's index is 1.
68   if (contents_stream_) {
69     CPDF_Array* new_contents_array = doc_->NewIndirect<CPDF_Array>();
70     new_contents_array->AddNew<CPDF_Reference>(doc_.Get(),
71                                                contents_stream_->GetObjNum());
72     new_contents_array->AddNew<CPDF_Reference>(doc_.Get(),
73                                                new_stream->GetObjNum());
74 
75     CPDF_Dictionary* page_dict = obj_holder_->GetDict();
76     page_dict->SetNewFor<CPDF_Reference>("Contents", doc_.Get(),
77                                          new_contents_array->GetObjNum());
78     contents_array_.Reset(new_contents_array);
79     contents_stream_ = nullptr;
80     return 1;
81   }
82 
83   // If there is an array, just add the new stream to it, at the last position.
84   if (contents_array_) {
85     contents_array_->AddNew<CPDF_Reference>(doc_.Get(),
86                                             new_stream->GetObjNum());
87     return contents_array_->size() - 1;
88   }
89 
90   // There were no Contents, so add the new stream as the single Content stream.
91   // Its index is 0.
92   CPDF_Dictionary* page_dict = obj_holder_->GetDict();
93   page_dict->SetNewFor<CPDF_Reference>("Contents", doc_.Get(),
94                                        new_stream->GetObjNum());
95   contents_stream_.Reset(new_stream);
96   return 0;
97 }
98 
ScheduleRemoveStreamByIndex(size_t stream_index)99 void CPDF_PageContentManager::ScheduleRemoveStreamByIndex(size_t stream_index) {
100   streams_to_remove_.insert(stream_index);
101 }
102 
ExecuteScheduledRemovals()103 void CPDF_PageContentManager::ExecuteScheduledRemovals() {
104   // This method assumes there are no dirty streams in the
105   // CPDF_PageObjectHolder. If there were any, their indexes would need to be
106   // updated.
107   // Since this is only called by CPDF_PageContentGenerator::GenerateContent(),
108   // which cleans up the dirty streams first, this should always be true.
109   ASSERT(!obj_holder_->HasDirtyStreams());
110 
111   if (contents_stream_) {
112     // Only stream that can be removed is 0.
113     if (streams_to_remove_.find(0) != streams_to_remove_.end()) {
114       CPDF_Dictionary* page_dict = obj_holder_->GetDict();
115       page_dict->RemoveFor("Contents");
116       contents_stream_ = nullptr;
117     }
118   } else if (contents_array_) {
119     // Initialize a vector with the old stream indexes. This will be used to
120     // build a map from the old to the new indexes.
121     std::vector<size_t> streams_left(contents_array_->size());
122     std::iota(streams_left.begin(), streams_left.end(), 0);
123 
124     // In reverse order so as to not change the indexes in the middle of the
125     // loop, remove the streams.
126     for (auto it = streams_to_remove_.rbegin(); it != streams_to_remove_.rend();
127          ++it) {
128       size_t stream_index = *it;
129       contents_array_->RemoveAt(stream_index);
130       streams_left.erase(streams_left.begin() + stream_index);
131     }
132 
133     // Create a mapping from the old to the new stream indexes, shifted due to
134     // the deletion of the |streams_to_remove_|.
135     std::map<int32_t, size_t> stream_index_mapping;
136     for (size_t i = 0; i < streams_left.size(); ++i)
137       stream_index_mapping[streams_left[i]] = i;
138 
139     // Update the page objects' content stream indexes.
140     for (const auto& obj : *obj_holder_) {
141       int32_t old_stream_index = obj->GetContentStream();
142       size_t new_stream_index = stream_index_mapping[old_stream_index];
143       obj->SetContentStream(new_stream_index);
144     }
145 
146     // Even if there is a single content stream now, keep the array with a
147     // single element. It's valid, a second stream might be added soon, and the
148     // complexity of removing it is not worth it.
149   }
150 
151   streams_to_remove_.clear();
152 }
153