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