• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright 2024 The PDFium Authors
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 // Original code copyright 2014 Foxit Software Inc. http://www.foxitsoftware.com
6 
7 #include "core/fpdfapi/edit/cpdf_npagetooneexporter.h"
8 
9 #include <algorithm>
10 #include <memory>
11 #include <sstream>
12 #include <utility>
13 
14 #include "constants/page_object.h"
15 #include "core/fpdfapi/edit/cpdf_contentstream_write_utils.h"
16 #include "core/fpdfapi/page/cpdf_page.h"
17 #include "core/fpdfapi/parser/cpdf_array.h"
18 #include "core/fpdfapi/parser/cpdf_dictionary.h"
19 #include "core/fpdfapi/parser/cpdf_document.h"
20 #include "core/fpdfapi/parser/cpdf_name.h"
21 #include "core/fpdfapi/parser/cpdf_number.h"
22 #include "core/fpdfapi/parser/cpdf_object.h"
23 #include "core/fpdfapi/parser/cpdf_reference.h"
24 #include "core/fpdfapi/parser/cpdf_stream.h"
25 #include "core/fpdfapi/parser/cpdf_stream_acc.h"
26 #include "core/fxcrt/check.h"
27 #include "core/fxcrt/fx_safe_types.h"
28 #include "core/fxcrt/fx_string_wrappers.h"
29 #include "core/fxcrt/span.h"
30 
31 namespace {
32 
33 // Calculates the N-up parameters.  When importing multiple pages into one page.
34 // The space of output page is evenly divided along the X axis and Y axis based
35 // on the input `pages_on_x_axis` and `pages_on_y_axis`.
36 class NupState {
37  public:
38   NupState(const CFX_SizeF& pagesize,
39            size_t pages_on_x_axis,
40            size_t pages_on_y_axis);
41 
42   // Calculate sub page origin and scale with the source page of `pagesize` and
43   // new page of `sub_page_size_`.
44   CPDF_NPageToOneExporter::NupPageSettings CalculateNewPagePosition(
45       const CFX_SizeF& pagesize);
46 
47  private:
48   // Helper function to get the `sub_x`, `sub_y` pair based on
49   // `sub_page_index_`. The space of output page is evenly divided into slots
50   // along x and y axis. `sub_x` and `sub_y` are 0-based indices that indicate
51   // which allocation slot to use.
52   std::pair<size_t, size_t> ConvertPageOrder() const;
53 
54   // Given the `sub_x` and `sub_y` subpage position within a page, and a source
55   // page with dimensions of `pagesize`, calculate the sub page's origin and
56   // scale.
57   CPDF_NPageToOneExporter::NupPageSettings CalculatePageEdit(
58       size_t sub_x,
59       size_t sub_y,
60       const CFX_SizeF& pagesize) const;
61 
62   const CFX_SizeF dest_page_size_;
63   const size_t pages_on_x_axis_;
64   const size_t pages_on_y_axis_;
65   const size_t pages_per_sheet_;
66   CFX_SizeF sub_page_size_;
67 
68   // A 0-based index, in range of [0, pages_per_sheet_ - 1).
69   size_t sub_page_index_ = 0;
70 };
71 
NupState(const CFX_SizeF & pagesize,size_t pages_on_x_axis,size_t pages_on_y_axis)72 NupState::NupState(const CFX_SizeF& pagesize,
73                    size_t pages_on_x_axis,
74                    size_t pages_on_y_axis)
75     : dest_page_size_(pagesize),
76       pages_on_x_axis_(pages_on_x_axis),
77       pages_on_y_axis_(pages_on_y_axis),
78       pages_per_sheet_(pages_on_x_axis * pages_on_y_axis) {
79   DCHECK(pages_on_x_axis_ > 0);
80   DCHECK(pages_on_y_axis_ > 0);
81   DCHECK(dest_page_size_.width > 0);
82   DCHECK(dest_page_size_.height > 0);
83 
84   sub_page_size_.width = dest_page_size_.width / pages_on_x_axis_;
85   sub_page_size_.height = dest_page_size_.height / pages_on_y_axis_;
86 }
87 
ConvertPageOrder() const88 std::pair<size_t, size_t> NupState::ConvertPageOrder() const {
89   size_t sub_x = sub_page_index_ % pages_on_x_axis_;
90   size_t sub_y = sub_page_index_ / pages_on_x_axis_;
91 
92   // Y Axis, pages start from the top of the output page.
93   sub_y = pages_on_y_axis_ - sub_y - 1;
94 
95   return {sub_x, sub_y};
96 }
97 
CalculatePageEdit(size_t sub_x,size_t sub_y,const CFX_SizeF & pagesize) const98 CPDF_NPageToOneExporter::NupPageSettings NupState::CalculatePageEdit(
99     size_t sub_x,
100     size_t sub_y,
101     const CFX_SizeF& pagesize) const {
102   CPDF_NPageToOneExporter::NupPageSettings settings;
103   settings.sub_page_start_point.x = sub_x * sub_page_size_.width;
104   settings.sub_page_start_point.y = sub_y * sub_page_size_.height;
105 
106   const float x_scale = sub_page_size_.width / pagesize.width;
107   const float y_scale = sub_page_size_.height / pagesize.height;
108   settings.scale = std::min(x_scale, y_scale);
109 
110   float sub_width = pagesize.width * settings.scale;
111   float sub_height = pagesize.height * settings.scale;
112   if (x_scale > y_scale) {
113     settings.sub_page_start_point.x += (sub_page_size_.width - sub_width) / 2;
114   } else {
115     settings.sub_page_start_point.y += (sub_page_size_.height - sub_height) / 2;
116   }
117   return settings;
118 }
119 
CalculateNewPagePosition(const CFX_SizeF & pagesize)120 CPDF_NPageToOneExporter::NupPageSettings NupState::CalculateNewPagePosition(
121     const CFX_SizeF& pagesize) {
122   if (sub_page_index_ >= pages_per_sheet_) {
123     sub_page_index_ = 0;
124   }
125 
126   auto [sub_x, sub_y] = ConvertPageOrder();
127   ++sub_page_index_;
128   return CalculatePageEdit(sub_x, sub_y, pagesize);
129 }
130 
131 // Helper that generates the content stream for a sub-page.
GenerateSubPageContentStream(const ByteString & xobject_name,const CPDF_NPageToOneExporter::NupPageSettings & settings)132 ByteString GenerateSubPageContentStream(
133     const ByteString& xobject_name,
134     const CPDF_NPageToOneExporter::NupPageSettings& settings) {
135   CFX_Matrix matrix;
136   matrix.Scale(settings.scale, settings.scale);
137   matrix.Translate(settings.sub_page_start_point.x,
138                    settings.sub_page_start_point.y);
139 
140   fxcrt::ostringstream content_stream;
141   content_stream << "q\n";
142   WriteMatrix(content_stream, matrix) << " cm\n"
143                                       << "/" << xobject_name << " Do Q\n";
144   return ByteString(content_stream);
145 }
146 
147 }  // namespace
148 
CPDF_NPageToOneExporter(CPDF_Document * dest_doc,CPDF_Document * src_doc)149 CPDF_NPageToOneExporter::CPDF_NPageToOneExporter(CPDF_Document* dest_doc,
150                                                  CPDF_Document* src_doc)
151     : CPDF_PageOrganizer(dest_doc, src_doc) {}
152 
153 CPDF_NPageToOneExporter::~CPDF_NPageToOneExporter() = default;
154 
ExportNPagesToOne(pdfium::span<const uint32_t> page_indices,const CFX_SizeF & dest_page_size,size_t pages_on_x_axis,size_t pages_on_y_axis)155 bool CPDF_NPageToOneExporter::ExportNPagesToOne(
156     pdfium::span<const uint32_t> page_indices,
157     const CFX_SizeF& dest_page_size,
158     size_t pages_on_x_axis,
159     size_t pages_on_y_axis) {
160   if (!Init()) {
161     return false;
162   }
163 
164   FX_SAFE_SIZE_T safe_pages_per_sheet = pages_on_x_axis;
165   safe_pages_per_sheet *= pages_on_y_axis;
166   if (!safe_pages_per_sheet.IsValid()) {
167     return false;
168   }
169 
170   ClearObjectNumberMap();
171   src_page_xobject_map_.clear();
172   size_t pages_per_sheet = safe_pages_per_sheet.ValueOrDie();
173   NupState n_up_state(dest_page_size, pages_on_x_axis, pages_on_y_axis);
174 
175   FX_SAFE_INT32 curpage = 0;
176   const CFX_FloatRect dest_page_rect(0, 0, dest_page_size.width,
177                                      dest_page_size.height);
178   for (size_t outer_page_index = 0; outer_page_index < page_indices.size();
179        outer_page_index += pages_per_sheet) {
180     xobject_name_to_number_map_.clear();
181 
182     RetainPtr<CPDF_Dictionary> dest_page_dict =
183         dest()->CreateNewPage(curpage.ValueOrDie());
184     if (!dest_page_dict) {
185       return false;
186     }
187 
188     dest_page_dict->SetRectFor(pdfium::page_object::kMediaBox, dest_page_rect);
189     ByteString content;
190     size_t inner_page_max =
191         std::min(outer_page_index + pages_per_sheet, page_indices.size());
192     for (size_t i = outer_page_index; i < inner_page_max; ++i) {
193       RetainPtr<CPDF_Dictionary> src_page_dict =
194           src()->GetMutablePageDictionary(page_indices[i]);
195       if (!src_page_dict) {
196         return false;
197       }
198 
199       auto src_page = pdfium::MakeRetain<CPDF_Page>(src(), src_page_dict);
200       src_page->AddPageImageCache();
201       NupPageSettings settings =
202           n_up_state.CalculateNewPagePosition(src_page->GetPageSize());
203       content += AddSubPage(src_page, settings);
204     }
205 
206     FinishPage(dest_page_dict, content);
207     ++curpage;
208   }
209 
210   return true;
211 }
212 
213 // static
GenerateSubPageContentStreamForTesting(const ByteString & xobject_name,const NupPageSettings & settings)214 ByteString CPDF_NPageToOneExporter::GenerateSubPageContentStreamForTesting(
215     const ByteString& xobject_name,
216     const NupPageSettings& settings) {
217   return GenerateSubPageContentStream(xobject_name, settings);
218 }
219 
AddSubPage(const RetainPtr<CPDF_Page> & src_page,const NupPageSettings & settings)220 ByteString CPDF_NPageToOneExporter::AddSubPage(
221     const RetainPtr<CPDF_Page>& src_page,
222     const NupPageSettings& settings) {
223   uint32_t src_page_obj_num = src_page->GetDict()->GetObjNum();
224   const auto it = src_page_xobject_map_.find(src_page_obj_num);
225   ByteString xobject_name = it != src_page_xobject_map_.end()
226                                 ? it->second
227                                 : MakeXObjectFromPage(src_page);
228   return GenerateSubPageContentStream(xobject_name, settings);
229 }
230 
MakeXObjectFromPageRaw(RetainPtr<CPDF_Page> src_page)231 RetainPtr<CPDF_Stream> CPDF_NPageToOneExporter::MakeXObjectFromPageRaw(
232     RetainPtr<CPDF_Page> src_page) {
233   RetainPtr<const CPDF_Dictionary> src_page_dict = src_page->GetDict();
234   RetainPtr<const CPDF_Object> src_contents =
235       src_page_dict->GetDirectObjectFor(pdfium::page_object::kContents);
236 
237   auto new_xobject =
238       dest()->NewIndirect<CPDF_Stream>(dest()->New<CPDF_Dictionary>());
239   RetainPtr<CPDF_Dictionary> new_xobject_dict = new_xobject->GetMutableDict();
240   static const char kResourceString[] = "Resources";
241   if (!CPDF_PageOrganizer::CopyInheritable(new_xobject_dict, src_page_dict,
242                                            kResourceString)) {
243     // Use a default empty resources if it does not exist.
244     new_xobject_dict->SetNewFor<CPDF_Dictionary>(kResourceString);
245   }
246   uint32_t src_page_obj_num = src_page_dict->GetObjNum();
247   uint32_t new_xobject_obj_num = new_xobject_dict->GetObjNum();
248   AddObjectMapping(src_page_obj_num, new_xobject_obj_num);
249   UpdateReference(new_xobject_dict);
250   new_xobject_dict->SetNewFor<CPDF_Name>("Type", "XObject");
251   new_xobject_dict->SetNewFor<CPDF_Name>("Subtype", "Form");
252   new_xobject_dict->SetNewFor<CPDF_Number>("FormType", 1);
253   new_xobject_dict->SetRectFor("BBox", src_page->GetBBox());
254   new_xobject_dict->SetMatrixFor("Matrix", src_page->GetPageMatrix());
255   if (!src_contents) {
256     return new_xobject;
257   }
258   const CPDF_Array* src_contents_array = src_contents->AsArray();
259   if (!src_contents_array) {
260     RetainPtr<const CPDF_Stream> stream(src_contents->AsStream());
261     auto acc = pdfium::MakeRetain<CPDF_StreamAcc>(std::move(stream));
262     acc->LoadAllDataFiltered();
263     new_xobject->SetDataAndRemoveFilter(acc->GetSpan());
264     return new_xobject;
265   }
266   ByteString src_content_stream;
267   for (size_t i = 0; i < src_contents_array->size(); ++i) {
268     RetainPtr<const CPDF_Stream> stream = src_contents_array->GetStreamAt(i);
269     auto acc = pdfium::MakeRetain<CPDF_StreamAcc>(std::move(stream));
270     acc->LoadAllDataFiltered();
271     src_content_stream += ByteStringView(acc->GetSpan());
272     src_content_stream += "\n";
273   }
274   new_xobject->SetDataAndRemoveFilter(src_content_stream.unsigned_span());
275   return new_xobject;
276 }
277 
MakeXObjectFromPage(RetainPtr<CPDF_Page> src_page)278 ByteString CPDF_NPageToOneExporter::MakeXObjectFromPage(
279     RetainPtr<CPDF_Page> src_page) {
280   RetainPtr<CPDF_Stream> new_xobject = MakeXObjectFromPageRaw(src_page);
281 
282   // TODO(xlou): A better name schema to avoid possible object name collision.
283   ByteString xobject_name = ByteString::Format("X%d", ++object_number_);
284   xobject_name_to_number_map_[xobject_name] = new_xobject->GetObjNum();
285   src_page_xobject_map_[src_page->GetDict()->GetObjNum()] = xobject_name;
286   return xobject_name;
287 }
288 
289 std::unique_ptr<XObjectContext>
CreateXObjectContextFromPage(int src_page_index)290 CPDF_NPageToOneExporter::CreateXObjectContextFromPage(int src_page_index) {
291   RetainPtr<CPDF_Dictionary> src_page_dict =
292       src()->GetMutablePageDictionary(src_page_index);
293   if (!src_page_dict) {
294     return nullptr;
295   }
296 
297   auto src_page = pdfium::MakeRetain<CPDF_Page>(src(), src_page_dict);
298   auto xobject = std::make_unique<XObjectContext>();
299   xobject->dest_doc = dest();
300   xobject->xobject.Reset(MakeXObjectFromPageRaw(src_page));
301   return xobject;
302 }
303 
FinishPage(RetainPtr<CPDF_Dictionary> dest_page_dict,const ByteString & content)304 void CPDF_NPageToOneExporter::FinishPage(
305     RetainPtr<CPDF_Dictionary> dest_page_dict,
306     const ByteString& content) {
307   RetainPtr<CPDF_Dictionary> resources =
308       dest_page_dict->GetOrCreateDictFor(pdfium::page_object::kResources);
309   RetainPtr<CPDF_Dictionary> xobject = resources->GetOrCreateDictFor("XObject");
310   for (auto& it : xobject_name_to_number_map_) {
311     xobject->SetNewFor<CPDF_Reference>(it.first, dest(), it.second);
312   }
313 
314   auto stream =
315       dest()->NewIndirect<CPDF_Stream>(dest()->New<CPDF_Dictionary>());
316   stream->SetData(content.unsigned_span());
317   dest_page_dict->SetNewFor<CPDF_Reference>(pdfium::page_object::kContents,
318                                             dest(), stream->GetObjNum());
319 }
320 
321 XObjectContext::XObjectContext() = default;
322 
323 XObjectContext::~XObjectContext() = default;
324