xref: /aosp_15_r20/external/pdfium/core/fpdfapi/edit/cpdf_pagecontentgenerator.cpp (revision 3ac0a46f773bac49fa9476ec2b1cf3f8da5ec3a4)
1 // Copyright 2016 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_pagecontentgenerator.h"
8 
9 #include <map>
10 #include <memory>
11 #include <set>
12 #include <sstream>
13 #include <tuple>
14 #include <utility>
15 
16 #include "constants/page_object.h"
17 #include "core/fpdfapi/edit/cpdf_contentstream_write_utils.h"
18 #include "core/fpdfapi/edit/cpdf_pagecontentmanager.h"
19 #include "core/fpdfapi/edit/cpdf_stringarchivestream.h"
20 #include "core/fpdfapi/font/cpdf_truetypefont.h"
21 #include "core/fpdfapi/font/cpdf_type1font.h"
22 #include "core/fpdfapi/page/cpdf_contentmarks.h"
23 #include "core/fpdfapi/page/cpdf_docpagedata.h"
24 #include "core/fpdfapi/page/cpdf_form.h"
25 #include "core/fpdfapi/page/cpdf_formobject.h"
26 #include "core/fpdfapi/page/cpdf_image.h"
27 #include "core/fpdfapi/page/cpdf_imageobject.h"
28 #include "core/fpdfapi/page/cpdf_page.h"
29 #include "core/fpdfapi/page/cpdf_path.h"
30 #include "core/fpdfapi/page/cpdf_pathobject.h"
31 #include "core/fpdfapi/page/cpdf_textobject.h"
32 #include "core/fpdfapi/parser/cpdf_array.h"
33 #include "core/fpdfapi/parser/cpdf_dictionary.h"
34 #include "core/fpdfapi/parser/cpdf_document.h"
35 #include "core/fpdfapi/parser/cpdf_name.h"
36 #include "core/fpdfapi/parser/cpdf_number.h"
37 #include "core/fpdfapi/parser/cpdf_reference.h"
38 #include "core/fpdfapi/parser/cpdf_stream.h"
39 #include "core/fpdfapi/parser/fpdf_parser_decode.h"
40 #include "core/fpdfapi/parser/fpdf_parser_utility.h"
41 #include "core/fpdfapi/parser/object_tree_traversal_util.h"
42 #include "third_party/base/check.h"
43 #include "third_party/base/containers/contains.h"
44 #include "third_party/base/containers/span.h"
45 #include "third_party/base/notreached.h"
46 #include "third_party/base/numerics/safe_conversions.h"
47 
48 namespace {
49 
50 // Key: The resource type.
51 // Value: The resource names of a given type.
52 using ResourcesMap = std::map<ByteString, std::set<ByteString>>;
53 
GetColor(const CPDF_Color * pColor,float * rgb)54 bool GetColor(const CPDF_Color* pColor, float* rgb) {
55   int intRGB[3];
56   if (!pColor || !pColor->IsColorSpaceRGB() ||
57       !pColor->GetRGB(&intRGB[0], &intRGB[1], &intRGB[2])) {
58     return false;
59   }
60   rgb[0] = intRGB[0] / 255.0f;
61   rgb[1] = intRGB[1] / 255.0f;
62   rgb[2] = intRGB[2] / 255.0f;
63   return true;
64 }
65 
RecordPageObjectResourceUsage(const CPDF_PageObject * page_object,ResourcesMap & seen_resources)66 void RecordPageObjectResourceUsage(const CPDF_PageObject* page_object,
67                                    ResourcesMap& seen_resources) {
68   const ByteString& resource_name = page_object->GetResourceName();
69   if (!resource_name.IsEmpty()) {
70     switch (page_object->GetType()) {
71       case CPDF_PageObject::Type::kText:
72         seen_resources["Font"].insert(resource_name);
73         break;
74       case CPDF_PageObject::Type::kImage:
75       case CPDF_PageObject::Type::kForm:
76         seen_resources["XObject"].insert(resource_name);
77         break;
78       case CPDF_PageObject::Type::kPath:
79         break;
80       case CPDF_PageObject::Type::kShading:
81         break;
82     }
83   }
84   for (const auto& name : page_object->GetGraphicsResourceNames()) {
85     CHECK(!name.IsEmpty());
86     seen_resources["ExtGState"].insert(name);
87   }
88 }
89 
RemoveUnusedResources(RetainPtr<CPDF_Dictionary> resources_dict,const ResourcesMap & resources_in_use)90 void RemoveUnusedResources(RetainPtr<CPDF_Dictionary> resources_dict,
91                            const ResourcesMap& resources_in_use) {
92   // TODO(thestig): Remove other unused resource types:
93   // - ColorSpace
94   // - Pattern
95   // - Shading
96   static constexpr const char* kResourceKeys[] = {"ExtGState", "Font",
97                                                   "XObject"};
98   for (const char* resource_key : kResourceKeys) {
99     RetainPtr<CPDF_Dictionary> resource_dict =
100         resources_dict->GetMutableDictFor(resource_key);
101     if (!resource_dict) {
102       continue;
103     }
104 
105     std::vector<ByteString> keys;
106     {
107       CPDF_DictionaryLocker resource_dict_locker(resource_dict);
108       for (auto& it : resource_dict_locker) {
109         keys.push_back(it.first);
110       }
111     }
112 
113     auto it = resources_in_use.find(resource_key);
114     const std::set<ByteString>* resource_in_use_of_current_type =
115         it != resources_in_use.end() ? &it->second : nullptr;
116     for (const ByteString& key : keys) {
117       if (resource_in_use_of_current_type &&
118           pdfium::Contains(*resource_in_use_of_current_type, key)) {
119         continue;
120       }
121 
122       resource_dict->RemoveFor(key.AsStringView());
123     }
124   }
125 }
126 
127 }  // namespace
128 
CPDF_PageContentGenerator(CPDF_PageObjectHolder * pObjHolder)129 CPDF_PageContentGenerator::CPDF_PageContentGenerator(
130     CPDF_PageObjectHolder* pObjHolder)
131     : m_pObjHolder(pObjHolder), m_pDocument(pObjHolder->GetDocument()) {
132   for (const auto& pObj : *pObjHolder) {
133     if (pObj)
134       m_pageObjects.emplace_back(pObj.get());
135   }
136 }
137 
138 CPDF_PageContentGenerator::~CPDF_PageContentGenerator() = default;
139 
GenerateContent()140 void CPDF_PageContentGenerator::GenerateContent() {
141   DCHECK(m_pObjHolder->IsPage());
142   std::map<int32_t, fxcrt::ostringstream> new_stream_data =
143       GenerateModifiedStreams();
144   // If no streams were regenerated or removed, nothing to do here.
145   if (new_stream_data.empty()) {
146     return;
147   }
148 
149   UpdateContentStreams(std::move(new_stream_data));
150   UpdateResourcesDict();
151 }
152 
153 std::map<int32_t, fxcrt::ostringstream>
GenerateModifiedStreams()154 CPDF_PageContentGenerator::GenerateModifiedStreams() {
155   // Figure out which streams are dirty.
156   std::set<int32_t> all_dirty_streams;
157   for (auto& pPageObj : m_pageObjects) {
158     if (pPageObj->IsDirty())
159       all_dirty_streams.insert(pPageObj->GetContentStream());
160   }
161   std::set<int32_t> marked_dirty_streams = m_pObjHolder->TakeDirtyStreams();
162   all_dirty_streams.insert(marked_dirty_streams.begin(),
163                            marked_dirty_streams.end());
164 
165   // Start regenerating dirty streams.
166   std::map<int32_t, fxcrt::ostringstream> streams;
167   std::set<int32_t> empty_streams;
168   std::unique_ptr<const CPDF_ContentMarks> empty_content_marks =
169       std::make_unique<CPDF_ContentMarks>();
170   std::map<int32_t, const CPDF_ContentMarks*> current_content_marks;
171 
172   for (int32_t dirty_stream : all_dirty_streams) {
173     fxcrt::ostringstream buf;
174 
175     // Set the default graphic state values
176     buf << "q\n";
177     if (!m_pObjHolder->GetLastCTM().IsIdentity())
178       WriteMatrix(buf, m_pObjHolder->GetLastCTM().GetInverse()) << " cm\n";
179 
180     ProcessDefaultGraphics(&buf);
181     streams[dirty_stream] = std::move(buf);
182     empty_streams.insert(dirty_stream);
183     current_content_marks[dirty_stream] = empty_content_marks.get();
184   }
185 
186   // Process the page objects, write into each dirty stream.
187   for (auto& pPageObj : m_pageObjects) {
188     int stream_index = pPageObj->GetContentStream();
189     auto it = streams.find(stream_index);
190     if (it == streams.end())
191       continue;
192 
193     fxcrt::ostringstream* buf = &it->second;
194     empty_streams.erase(stream_index);
195     current_content_marks[stream_index] =
196         ProcessContentMarks(buf, pPageObj, current_content_marks[stream_index]);
197     ProcessPageObject(buf, pPageObj);
198   }
199 
200   // Finish dirty streams.
201   for (int32_t dirty_stream : all_dirty_streams) {
202     fxcrt::ostringstream* buf = &streams[dirty_stream];
203     if (pdfium::Contains(empty_streams, dirty_stream)) {
204       // Clear to show that this stream needs to be deleted.
205       buf->str("");
206     } else {
207       FinishMarks(buf, current_content_marks[dirty_stream]);
208 
209       // Return graphics to original state
210       *buf << "Q\n";
211     }
212   }
213 
214   return streams;
215 }
216 
UpdateContentStreams(std::map<int32_t,fxcrt::ostringstream> && new_stream_data)217 void CPDF_PageContentGenerator::UpdateContentStreams(
218     std::map<int32_t, fxcrt::ostringstream>&& new_stream_data) {
219   CHECK(!new_stream_data.empty());
220 
221   // Make sure default graphics are created.
222   m_DefaultGraphicsName = GetOrCreateDefaultGraphics();
223 
224   CPDF_PageContentManager page_content_manager(m_pObjHolder, m_pDocument);
225   for (auto& pair : new_stream_data) {
226     int32_t stream_index = pair.first;
227     fxcrt::ostringstream* buf = &pair.second;
228 
229     if (stream_index == CPDF_PageObject::kNoContentStream) {
230       int new_stream_index =
231           pdfium::base::checked_cast<int>(page_content_manager.AddStream(buf));
232       UpdateStreamlessPageObjects(new_stream_index);
233       continue;
234     }
235 
236     page_content_manager.UpdateStream(stream_index, buf);
237   }
238 }
239 
UpdateResourcesDict()240 void CPDF_PageContentGenerator::UpdateResourcesDict() {
241   RetainPtr<CPDF_Dictionary> resources = m_pObjHolder->GetMutableResources();
242   if (!resources) {
243     return;
244   }
245 
246   const uint32_t resources_object_number = resources->GetObjNum();
247   if (resources_object_number) {
248     // If `resources` is not an inline object, then do not modify it directly if
249     // it has multiple references.
250     if (pdfium::Contains(GetObjectsWithMultipleReferences(m_pDocument),
251                          resources_object_number)) {
252       resources = pdfium::WrapRetain(resources->Clone()->AsMutableDictionary());
253       const uint32_t clone_object_number =
254           m_pDocument->AddIndirectObject(resources);
255       m_pObjHolder->SetResources(resources);
256       m_pObjHolder->GetMutableDict()->SetNewFor<CPDF_Reference>(
257           pdfium::page_object::kResources, m_pDocument, clone_object_number);
258     }
259   }
260 
261   ResourcesMap seen_resources;
262   for (auto& page_object : m_pageObjects) {
263     RecordPageObjectResourceUsage(page_object, seen_resources);
264   }
265   if (!m_DefaultGraphicsName.IsEmpty()) {
266     seen_resources["ExtGState"].insert(m_DefaultGraphicsName);
267   }
268 
269   RemoveUnusedResources(std::move(resources), seen_resources);
270 }
271 
RealizeResource(const CPDF_Object * pResource,const ByteString & bsType) const272 ByteString CPDF_PageContentGenerator::RealizeResource(
273     const CPDF_Object* pResource,
274     const ByteString& bsType) const {
275   DCHECK(pResource);
276   if (!m_pObjHolder->GetResources()) {
277     m_pObjHolder->SetResources(m_pDocument->NewIndirect<CPDF_Dictionary>());
278     m_pObjHolder->GetMutableDict()->SetNewFor<CPDF_Reference>(
279         pdfium::page_object::kResources, m_pDocument,
280         m_pObjHolder->GetResources()->GetObjNum());
281   }
282 
283   RetainPtr<CPDF_Dictionary> pResList =
284       m_pObjHolder->GetMutableResources()->GetOrCreateDictFor(bsType);
285   ByteString name;
286   int idnum = 1;
287   while (true) {
288     name = ByteString::Format("FX%c%d", bsType[0], idnum);
289     if (!pResList->KeyExist(name))
290       break;
291 
292     idnum++;
293   }
294   pResList->SetNewFor<CPDF_Reference>(name, m_pDocument,
295                                       pResource->GetObjNum());
296   return name;
297 }
298 
ProcessPageObjects(fxcrt::ostringstream * buf)299 bool CPDF_PageContentGenerator::ProcessPageObjects(fxcrt::ostringstream* buf) {
300   bool bDirty = false;
301   std::unique_ptr<const CPDF_ContentMarks> empty_content_marks =
302       std::make_unique<CPDF_ContentMarks>();
303   const CPDF_ContentMarks* content_marks = empty_content_marks.get();
304 
305   for (auto& pPageObj : m_pageObjects) {
306     if (m_pObjHolder->IsPage() && !pPageObj->IsDirty())
307       continue;
308 
309     bDirty = true;
310     content_marks = ProcessContentMarks(buf, pPageObj, content_marks);
311     ProcessPageObject(buf, pPageObj);
312   }
313   FinishMarks(buf, content_marks);
314   return bDirty;
315 }
316 
UpdateStreamlessPageObjects(int new_content_stream_index)317 void CPDF_PageContentGenerator::UpdateStreamlessPageObjects(
318     int new_content_stream_index) {
319   for (auto& pPageObj : m_pageObjects) {
320     if (pPageObj->GetContentStream() == CPDF_PageObject::kNoContentStream)
321       pPageObj->SetContentStream(new_content_stream_index);
322   }
323 }
324 
ProcessContentMarks(fxcrt::ostringstream * buf,const CPDF_PageObject * pPageObj,const CPDF_ContentMarks * pPrev)325 const CPDF_ContentMarks* CPDF_PageContentGenerator::ProcessContentMarks(
326     fxcrt::ostringstream* buf,
327     const CPDF_PageObject* pPageObj,
328     const CPDF_ContentMarks* pPrev) {
329   const CPDF_ContentMarks* pNext = pPageObj->GetContentMarks();
330   const size_t first_different = pPrev->FindFirstDifference(pNext);
331 
332   // Close all marks that are in prev but not in next.
333   // Technically we should iterate backwards to close from the top to the
334   // bottom, but since the EMC operators do not identify which mark they are
335   // closing, it does not matter.
336   for (size_t i = first_different; i < pPrev->CountItems(); ++i)
337     *buf << "EMC\n";
338 
339   // Open all marks that are in next but not in prev.
340   for (size_t i = first_different; i < pNext->CountItems(); ++i) {
341     const CPDF_ContentMarkItem* item = pNext->GetItem(i);
342 
343     // Write mark tag.
344     *buf << "/" << PDF_NameEncode(item->GetName()) << " ";
345 
346     // If there are no parameters, write a BMC (begin marked content) operator.
347     if (item->GetParamType() == CPDF_ContentMarkItem::kNone) {
348       *buf << "BMC\n";
349       continue;
350     }
351 
352     // If there are parameters, write properties, direct or indirect.
353     switch (item->GetParamType()) {
354       case CPDF_ContentMarkItem::kDirectDict: {
355         CPDF_StringArchiveStream archive_stream(buf);
356         item->GetParam()->WriteTo(&archive_stream, nullptr);
357         *buf << " ";
358         break;
359       }
360       case CPDF_ContentMarkItem::kPropertiesDict: {
361         *buf << "/" << item->GetPropertyName() << " ";
362         break;
363       }
364       case CPDF_ContentMarkItem::kNone:
365         NOTREACHED_NORETURN();
366     }
367 
368     // Write BDC (begin dictionary content) operator.
369     *buf << "BDC\n";
370   }
371 
372   return pNext;
373 }
374 
FinishMarks(fxcrt::ostringstream * buf,const CPDF_ContentMarks * pContentMarks)375 void CPDF_PageContentGenerator::FinishMarks(
376     fxcrt::ostringstream* buf,
377     const CPDF_ContentMarks* pContentMarks) {
378   // Technically we should iterate backwards to close from the top to the
379   // bottom, but since the EMC operators do not identify which mark they are
380   // closing, it does not matter.
381   for (size_t i = 0; i < pContentMarks->CountItems(); ++i)
382     *buf << "EMC\n";
383 }
384 
ProcessPageObject(fxcrt::ostringstream * buf,CPDF_PageObject * pPageObj)385 void CPDF_PageContentGenerator::ProcessPageObject(fxcrt::ostringstream* buf,
386                                                   CPDF_PageObject* pPageObj) {
387   if (CPDF_ImageObject* pImageObject = pPageObj->AsImage())
388     ProcessImage(buf, pImageObject);
389   else if (CPDF_FormObject* pFormObj = pPageObj->AsForm())
390     ProcessForm(buf, pFormObj);
391   else if (CPDF_PathObject* pPathObj = pPageObj->AsPath())
392     ProcessPath(buf, pPathObj);
393   else if (CPDF_TextObject* pTextObj = pPageObj->AsText())
394     ProcessText(buf, pTextObj);
395   pPageObj->SetDirty(false);
396 }
397 
ProcessImage(fxcrt::ostringstream * buf,CPDF_ImageObject * pImageObj)398 void CPDF_PageContentGenerator::ProcessImage(fxcrt::ostringstream* buf,
399                                              CPDF_ImageObject* pImageObj) {
400   if ((pImageObj->matrix().a == 0 && pImageObj->matrix().b == 0) ||
401       (pImageObj->matrix().c == 0 && pImageObj->matrix().d == 0)) {
402     return;
403   }
404 
405   RetainPtr<CPDF_Image> pImage = pImageObj->GetImage();
406   if (pImage->IsInline())
407     return;
408 
409   RetainPtr<const CPDF_Stream> pStream = pImage->GetStream();
410   if (!pStream)
411     return;
412 
413   *buf << "q ";
414   WriteMatrix(*buf, pImageObj->matrix()) << " cm ";
415 
416   bool bWasInline = pStream->IsInline();
417   if (bWasInline)
418     pImage->ConvertStreamToIndirectObject();
419 
420   ByteString name = RealizeResource(pStream, "XObject");
421   pImageObj->SetResourceName(name);
422 
423   if (bWasInline) {
424     auto* pPageData = CPDF_DocPageData::FromDocument(m_pDocument);
425     pImageObj->SetImage(pPageData->GetImage(pStream->GetObjNum()));
426   }
427 
428   *buf << "/" << PDF_NameEncode(name) << " Do Q\n";
429 }
430 
ProcessForm(fxcrt::ostringstream * buf,CPDF_FormObject * pFormObj)431 void CPDF_PageContentGenerator::ProcessForm(fxcrt::ostringstream* buf,
432                                             CPDF_FormObject* pFormObj) {
433   if ((pFormObj->form_matrix().a == 0 && pFormObj->form_matrix().b == 0) ||
434       (pFormObj->form_matrix().c == 0 && pFormObj->form_matrix().d == 0)) {
435     return;
436   }
437 
438   RetainPtr<const CPDF_Stream> pStream = pFormObj->form()->GetStream();
439   if (!pStream)
440     return;
441 
442   ByteString name = RealizeResource(pStream.Get(), "XObject");
443   pFormObj->SetResourceName(name);
444 
445   *buf << "q\n";
446   WriteMatrix(*buf, pFormObj->form_matrix()) << " cm ";
447   *buf << "/" << PDF_NameEncode(name) << " Do Q\n";
448 }
449 
450 // Processing path construction with operators from Table 4.9 of PDF spec 1.7:
451 // "re" appends a rectangle (here, used only if the whole path is a rectangle)
452 // "m" moves current point to the given coordinates
453 // "l" creates a line from current point to the new point
454 // "c" adds a Bezier curve from current to last point, using the two other
455 // points as the Bezier control points
456 // Note: "l", "c" change the current point
457 // "h" closes the subpath (appends a line from current to starting point)
ProcessPathPoints(fxcrt::ostringstream * buf,CPDF_Path * pPath)458 void CPDF_PageContentGenerator::ProcessPathPoints(fxcrt::ostringstream* buf,
459                                                   CPDF_Path* pPath) {
460   pdfium::span<const CFX_Path::Point> points = pPath->GetPoints();
461   if (pPath->IsRect()) {
462     CFX_PointF diff = points[2].m_Point - points[0].m_Point;
463     WritePoint(*buf, points[0].m_Point) << " ";
464     WritePoint(*buf, diff) << " re";
465     return;
466   }
467   for (size_t i = 0; i < points.size(); ++i) {
468     if (i > 0)
469       *buf << " ";
470 
471     WritePoint(*buf, points[i].m_Point);
472 
473     CFX_Path::Point::Type point_type = points[i].m_Type;
474     if (point_type == CFX_Path::Point::Type::kMove) {
475       *buf << " m";
476     } else if (point_type == CFX_Path::Point::Type::kLine) {
477       *buf << " l";
478     } else if (point_type == CFX_Path::Point::Type::kBezier) {
479       if (i + 2 >= points.size() ||
480           !points[i].IsTypeAndOpen(CFX_Path::Point::Type::kBezier) ||
481           !points[i + 1].IsTypeAndOpen(CFX_Path::Point::Type::kBezier) ||
482           points[i + 2].m_Type != CFX_Path::Point::Type::kBezier) {
483         // If format is not supported, close the path and paint
484         *buf << " h";
485         break;
486       }
487       *buf << " ";
488       WritePoint(*buf, points[i + 1].m_Point) << " ";
489       WritePoint(*buf, points[i + 2].m_Point) << " c";
490       i += 2;
491     }
492     if (points[i].m_CloseFigure)
493       *buf << " h";
494   }
495 }
496 
497 // Processing path painting with operators from Table 4.10 of PDF spec 1.7:
498 // Path painting operators: "S", "n", "B", "f", "B*", "f*", depending on
499 // the filling mode and whether we want stroking the path or not.
500 // "Q" restores the graphics state imposed by the ProcessGraphics method.
ProcessPath(fxcrt::ostringstream * buf,CPDF_PathObject * pPathObj)501 void CPDF_PageContentGenerator::ProcessPath(fxcrt::ostringstream* buf,
502                                             CPDF_PathObject* pPathObj) {
503   ProcessGraphics(buf, pPathObj);
504 
505   WriteMatrix(*buf, pPathObj->matrix()) << " cm ";
506   ProcessPathPoints(buf, &pPathObj->path());
507 
508   if (pPathObj->has_no_filltype())
509     *buf << (pPathObj->stroke() ? " S" : " n");
510   else if (pPathObj->has_winding_filltype())
511     *buf << (pPathObj->stroke() ? " B" : " f");
512   else if (pPathObj->has_alternate_filltype())
513     *buf << (pPathObj->stroke() ? " B*" : " f*");
514   *buf << " Q\n";
515 }
516 
517 // This method supports color operators rg and RGB from Table 4.24 of PDF spec
518 // 1.7. A color will not be set if the colorspace is not DefaultRGB or the RGB
519 // values cannot be obtained. The method also adds an external graphics
520 // dictionary, as described in Section 4.3.4.
521 // "rg" sets the fill color, "RG" sets the stroke color (using DefaultRGB)
522 // "w" sets the stroke line width.
523 // "ca" sets the fill alpha, "CA" sets the stroke alpha.
524 // "W" and "W*" modify the clipping path using the nonzero winding rule and
525 // even-odd rules, respectively.
526 // "q" saves the graphics state, so that the settings can later be reversed
ProcessGraphics(fxcrt::ostringstream * buf,CPDF_PageObject * pPageObj)527 void CPDF_PageContentGenerator::ProcessGraphics(fxcrt::ostringstream* buf,
528                                                 CPDF_PageObject* pPageObj) {
529   *buf << "q ";
530   float fillColor[3];
531   if (GetColor(pPageObj->m_ColorState.GetFillColor(), fillColor)) {
532     *buf << fillColor[0] << " " << fillColor[1] << " " << fillColor[2]
533          << " rg ";
534   }
535   float strokeColor[3];
536   if (GetColor(pPageObj->m_ColorState.GetStrokeColor(), strokeColor)) {
537     *buf << strokeColor[0] << " " << strokeColor[1] << " " << strokeColor[2]
538          << " RG ";
539   }
540   float lineWidth = pPageObj->m_GraphState.GetLineWidth();
541   if (lineWidth != 1.0f)
542     WriteFloat(*buf, lineWidth) << " w ";
543   CFX_GraphStateData::LineCap lineCap = pPageObj->m_GraphState.GetLineCap();
544   if (lineCap != CFX_GraphStateData::LineCap::kButt)
545     *buf << static_cast<int>(lineCap) << " J ";
546   CFX_GraphStateData::LineJoin lineJoin = pPageObj->m_GraphState.GetLineJoin();
547   if (lineJoin != CFX_GraphStateData::LineJoin::kMiter)
548     *buf << static_cast<int>(lineJoin) << " j ";
549   std::vector<float> dash_array = pPageObj->m_GraphState.GetLineDashArray();
550   if (dash_array.size()) {
551     *buf << "[";
552     for (size_t i = 0; i < dash_array.size(); ++i) {
553       if (i > 0) {
554         *buf << " ";
555       }
556       WriteFloat(*buf, dash_array[i]);
557     }
558     *buf << "] ";
559     WriteFloat(*buf, pPageObj->m_GraphState.GetLineDashPhase()) << " d ";
560   }
561 
562   const CPDF_ClipPath& clip_path = pPageObj->m_ClipPath;
563   if (clip_path.HasRef()) {
564     for (size_t i = 0; i < clip_path.GetPathCount(); ++i) {
565       CPDF_Path path = clip_path.GetPath(i);
566       ProcessPathPoints(buf, &path);
567       switch (clip_path.GetClipType(i)) {
568         case CFX_FillRenderOptions::FillType::kWinding:
569           *buf << " W ";
570           break;
571         case CFX_FillRenderOptions::FillType::kEvenOdd:
572           *buf << " W* ";
573           break;
574         case CFX_FillRenderOptions::FillType::kNoFill:
575           NOTREACHED_NORETURN();
576       }
577 
578       // Use a no-op path-painting operator to terminate the path without
579       // causing any marks to be placed on the page.
580       *buf << "n ";
581     }
582   }
583 
584   GraphicsData graphD;
585   graphD.fillAlpha = pPageObj->m_GeneralState.GetFillAlpha();
586   graphD.strokeAlpha = pPageObj->m_GeneralState.GetStrokeAlpha();
587   graphD.blendType = pPageObj->m_GeneralState.GetBlendType();
588   if (graphD.fillAlpha == 1.0f && graphD.strokeAlpha == 1.0f &&
589       graphD.blendType == BlendMode::kNormal) {
590     return;
591   }
592 
593   ByteString name;
594   absl::optional<ByteString> maybe_name =
595       m_pObjHolder->GraphicsMapSearch(graphD);
596   if (maybe_name.has_value()) {
597     name = std::move(maybe_name.value());
598   } else {
599     auto gsDict = pdfium::MakeRetain<CPDF_Dictionary>();
600     if (graphD.fillAlpha != 1.0f)
601       gsDict->SetNewFor<CPDF_Number>("ca", graphD.fillAlpha);
602 
603     if (graphD.strokeAlpha != 1.0f)
604       gsDict->SetNewFor<CPDF_Number>("CA", graphD.strokeAlpha);
605 
606     if (graphD.blendType != BlendMode::kNormal) {
607       gsDict->SetNewFor<CPDF_Name>("BM",
608                                    pPageObj->m_GeneralState.GetBlendMode());
609     }
610     m_pDocument->AddIndirectObject(gsDict);
611     name = RealizeResource(std::move(gsDict), "ExtGState");
612     pPageObj->m_GeneralState.SetGraphicsResourceNames({name});
613     m_pObjHolder->GraphicsMapInsert(graphD, name);
614   }
615   *buf << "/" << PDF_NameEncode(name) << " gs ";
616 }
617 
ProcessDefaultGraphics(fxcrt::ostringstream * buf)618 void CPDF_PageContentGenerator::ProcessDefaultGraphics(
619     fxcrt::ostringstream* buf) {
620   *buf << "0 0 0 RG 0 0 0 rg 1 w "
621        << static_cast<int>(CFX_GraphStateData::LineCap::kButt) << " J "
622        << static_cast<int>(CFX_GraphStateData::LineJoin::kMiter) << " j\n";
623   m_DefaultGraphicsName = GetOrCreateDefaultGraphics();
624   *buf << "/" << PDF_NameEncode(m_DefaultGraphicsName) << " gs ";
625 }
626 
GetOrCreateDefaultGraphics() const627 ByteString CPDF_PageContentGenerator::GetOrCreateDefaultGraphics() const {
628   GraphicsData defaultGraphics;
629   defaultGraphics.fillAlpha = 1.0f;
630   defaultGraphics.strokeAlpha = 1.0f;
631   defaultGraphics.blendType = BlendMode::kNormal;
632 
633   absl::optional<ByteString> maybe_name =
634       m_pObjHolder->GraphicsMapSearch(defaultGraphics);
635   if (maybe_name.has_value())
636     return maybe_name.value();
637 
638   auto gsDict = pdfium::MakeRetain<CPDF_Dictionary>();
639   gsDict->SetNewFor<CPDF_Number>("ca", defaultGraphics.fillAlpha);
640   gsDict->SetNewFor<CPDF_Number>("CA", defaultGraphics.strokeAlpha);
641   gsDict->SetNewFor<CPDF_Name>("BM", "Normal");
642   m_pDocument->AddIndirectObject(gsDict);
643   ByteString name = RealizeResource(std::move(gsDict), "ExtGState");
644   m_pObjHolder->GraphicsMapInsert(defaultGraphics, name);
645   return name;
646 }
647 
648 // This method adds text to the buffer, BT begins the text object, ET ends it.
649 // Tm sets the text matrix (allows positioning and transforming text).
650 // Tf sets the font name (from Font in Resources) and font size.
651 // Tr sets the text rendering mode.
652 // Tj sets the actual text, <####...> is used when specifying charcodes.
ProcessText(fxcrt::ostringstream * buf,CPDF_TextObject * pTextObj)653 void CPDF_PageContentGenerator::ProcessText(fxcrt::ostringstream* buf,
654                                             CPDF_TextObject* pTextObj) {
655   ProcessGraphics(buf, pTextObj);
656   *buf << "BT ";
657   WriteMatrix(*buf, pTextObj->GetTextMatrix()) << " Tm ";
658   RetainPtr<CPDF_Font> pFont(pTextObj->GetFont());
659   if (!pFont)
660     pFont = CPDF_Font::GetStockFont(m_pDocument, "Helvetica");
661 
662   FontData data;
663   const CPDF_FontEncoding* pEncoding = nullptr;
664   if (pFont->IsType1Font()) {
665     data.type = "Type1";
666     pEncoding = pFont->AsType1Font()->GetEncoding();
667   } else if (pFont->IsTrueTypeFont()) {
668     data.type = "TrueType";
669     pEncoding = pFont->AsTrueTypeFont()->GetEncoding();
670   } else if (pFont->IsCIDFont()) {
671     data.type = "Type0";
672   } else {
673     return;
674   }
675   data.baseFont = pFont->GetBaseFontName();
676 
677   ByteString dict_name;
678   absl::optional<ByteString> maybe_name = m_pObjHolder->FontsMapSearch(data);
679   if (maybe_name.has_value()) {
680     dict_name = std::move(maybe_name.value());
681   } else {
682     RetainPtr<const CPDF_Object> pIndirectFont = pFont->GetFontDict();
683     if (pIndirectFont->IsInline()) {
684       // In this case we assume it must be a standard font
685       auto pFontDict = pdfium::MakeRetain<CPDF_Dictionary>();
686       pFontDict->SetNewFor<CPDF_Name>("Type", "Font");
687       pFontDict->SetNewFor<CPDF_Name>("Subtype", data.type);
688       pFontDict->SetNewFor<CPDF_Name>("BaseFont", data.baseFont);
689       if (pEncoding) {
690         pFontDict->SetFor("Encoding",
691                           pEncoding->Realize(m_pDocument->GetByteStringPool()));
692       }
693       m_pDocument->AddIndirectObject(pFontDict);
694       pIndirectFont = std::move(pFontDict);
695     }
696     dict_name = RealizeResource(std::move(pIndirectFont), "Font");
697     m_pObjHolder->FontsMapInsert(data, dict_name);
698   }
699   pTextObj->SetResourceName(dict_name);
700 
701   *buf << "/" << PDF_NameEncode(dict_name) << " ";
702   WriteFloat(*buf, pTextObj->GetFontSize()) << " Tf ";
703   *buf << static_cast<int>(pTextObj->GetTextRenderMode()) << " Tr ";
704   ByteString text;
705   for (uint32_t charcode : pTextObj->GetCharCodes()) {
706     if (charcode != CPDF_Font::kInvalidCharCode)
707       pFont->AppendChar(&text, charcode);
708   }
709   *buf << PDF_HexEncodeString(text.AsStringView()) << " Tj ET";
710   *buf << " Q\n";
711 }
712