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