• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright 2016 PDFium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 // 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 <tuple>
13 #include <utility>
14 
15 #include "core/fpdfapi/edit/cpdf_contentstream_write_utils.h"
16 #include "core/fpdfapi/edit/cpdf_pagecontentmanager.h"
17 #include "core/fpdfapi/edit/cpdf_stringarchivestream.h"
18 #include "core/fpdfapi/font/cpdf_truetypefont.h"
19 #include "core/fpdfapi/font/cpdf_type1font.h"
20 #include "core/fpdfapi/page/cpdf_contentmarks.h"
21 #include "core/fpdfapi/page/cpdf_docpagedata.h"
22 #include "core/fpdfapi/page/cpdf_image.h"
23 #include "core/fpdfapi/page/cpdf_imageobject.h"
24 #include "core/fpdfapi/page/cpdf_page.h"
25 #include "core/fpdfapi/page/cpdf_path.h"
26 #include "core/fpdfapi/page/cpdf_pathobject.h"
27 #include "core/fpdfapi/page/cpdf_textobject.h"
28 #include "core/fpdfapi/parser/cpdf_array.h"
29 #include "core/fpdfapi/parser/cpdf_dictionary.h"
30 #include "core/fpdfapi/parser/cpdf_document.h"
31 #include "core/fpdfapi/parser/cpdf_name.h"
32 #include "core/fpdfapi/parser/cpdf_number.h"
33 #include "core/fpdfapi/parser/cpdf_reference.h"
34 #include "core/fpdfapi/parser/cpdf_stream.h"
35 #include "core/fpdfapi/parser/fpdf_parser_decode.h"
36 #include "core/fpdfapi/parser/fpdf_parser_utility.h"
37 #include "third_party/base/ptr_util.h"
38 #include "third_party/base/stl_util.h"
39 
40 namespace {
41 
GetColor(const CPDF_Color * pColor,float * rgb)42 bool GetColor(const CPDF_Color* pColor, float* rgb) {
43   int intRGB[3];
44   if (!pColor || !pColor->IsColorSpaceRGB() ||
45       !pColor->GetRGB(&intRGB[0], &intRGB[1], &intRGB[2])) {
46     return false;
47   }
48   rgb[0] = intRGB[0] / 255.0f;
49   rgb[1] = intRGB[1] / 255.0f;
50   rgb[2] = intRGB[2] / 255.0f;
51   return true;
52 }
53 
54 }  // namespace
55 
CPDF_PageContentGenerator(CPDF_PageObjectHolder * pObjHolder)56 CPDF_PageContentGenerator::CPDF_PageContentGenerator(
57     CPDF_PageObjectHolder* pObjHolder)
58     : m_pObjHolder(pObjHolder), m_pDocument(pObjHolder->GetDocument()) {
59   for (const auto& pObj : *pObjHolder) {
60     if (pObj)
61       m_pageObjects.emplace_back(pObj.get());
62   }
63 }
64 
~CPDF_PageContentGenerator()65 CPDF_PageContentGenerator::~CPDF_PageContentGenerator() {}
66 
GenerateContent()67 void CPDF_PageContentGenerator::GenerateContent() {
68   ASSERT(m_pObjHolder->IsPage());
69 
70   std::map<int32_t, std::unique_ptr<std::ostringstream>> stream =
71       GenerateModifiedStreams();
72 
73   UpdateContentStreams(&stream);
74 }
75 
76 std::map<int32_t, std::unique_ptr<std::ostringstream>>
GenerateModifiedStreams()77 CPDF_PageContentGenerator::GenerateModifiedStreams() {
78   // Make sure default graphics are created.
79   GetOrCreateDefaultGraphics();
80 
81   // Figure out which streams are dirty.
82   std::set<int32_t> all_dirty_streams;
83   for (auto& pPageObj : m_pageObjects) {
84     if (pPageObj->IsDirty())
85       all_dirty_streams.insert(pPageObj->GetContentStream());
86   }
87   std::set<int32_t> marked_dirty_streams = m_pObjHolder->TakeDirtyStreams();
88   all_dirty_streams.insert(marked_dirty_streams.begin(),
89                            marked_dirty_streams.end());
90 
91   // Start regenerating dirty streams.
92   std::map<int32_t, std::unique_ptr<std::ostringstream>> streams;
93   std::set<int32_t> empty_streams;
94   std::unique_ptr<const CPDF_ContentMarks> empty_content_marks =
95       pdfium::MakeUnique<CPDF_ContentMarks>();
96   std::map<int32_t, const CPDF_ContentMarks*> current_content_marks;
97 
98   for (int32_t dirty_stream : all_dirty_streams) {
99     std::unique_ptr<std::ostringstream> buf =
100         pdfium::MakeUnique<std::ostringstream>();
101 
102     // Set the default graphic state values
103     *buf << "q\n";
104     if (!m_pObjHolder->GetLastCTM().IsIdentity())
105       *buf << m_pObjHolder->GetLastCTM().GetInverse() << " cm\n";
106 
107     ProcessDefaultGraphics(buf.get());
108 
109     streams[dirty_stream] = std::move(buf);
110     empty_streams.insert(dirty_stream);
111     current_content_marks[dirty_stream] = empty_content_marks.get();
112   }
113 
114   // Process the page objects, write into each dirty stream.
115   for (auto& pPageObj : m_pageObjects) {
116     int stream_index = pPageObj->GetContentStream();
117     auto it = streams.find(stream_index);
118     if (it == streams.end())
119       continue;
120 
121     std::ostringstream* buf = it->second.get();
122     empty_streams.erase(stream_index);
123     current_content_marks[stream_index] = ProcessContentMarks(
124         buf, pPageObj.Get(), current_content_marks[stream_index]);
125     ProcessPageObject(buf, pPageObj.Get());
126   }
127 
128   // Finish dirty streams.
129   for (int32_t dirty_stream : all_dirty_streams) {
130     std::ostringstream* buf = streams[dirty_stream].get();
131     if (pdfium::ContainsKey(empty_streams, dirty_stream)) {
132       // Clear to show that this stream needs to be deleted.
133       buf->str("");
134     } else {
135       FinishMarks(buf, current_content_marks[dirty_stream]);
136 
137       // Return graphics to original state
138       *buf << "Q\n";
139     }
140   }
141 
142   return streams;
143 }
144 
UpdateContentStreams(std::map<int32_t,std::unique_ptr<std::ostringstream>> * new_stream_data)145 void CPDF_PageContentGenerator::UpdateContentStreams(
146     std::map<int32_t, std::unique_ptr<std::ostringstream>>* new_stream_data) {
147   // If no streams were regenerated or removed, nothing to do here.
148   if (new_stream_data->empty())
149     return;
150 
151   CPDF_PageContentManager page_content_manager(m_pObjHolder.Get());
152 
153   for (auto& pair : *new_stream_data) {
154     int32_t stream_index = pair.first;
155     std::ostringstream* buf = pair.second.get();
156 
157     if (stream_index == CPDF_PageObject::kNoContentStream) {
158       int new_stream_index = page_content_manager.AddStream(buf);
159       UpdateStreamlessPageObjects(new_stream_index);
160       continue;
161     }
162 
163     CPDF_Stream* old_stream =
164         page_content_manager.GetStreamByIndex(stream_index);
165     ASSERT(old_stream);
166 
167     // If buf is now empty, remove the stream instead of setting the data.
168     if (buf->tellp() <= 0)
169       page_content_manager.ScheduleRemoveStreamByIndex(stream_index);
170     else
171       old_stream->SetDataFromStringstreamAndRemoveFilter(buf);
172   }
173 
174   page_content_manager.ExecuteScheduledRemovals();
175 }
176 
RealizeResource(const CPDF_Object * pResource,const ByteString & bsType) const177 ByteString CPDF_PageContentGenerator::RealizeResource(
178     const CPDF_Object* pResource,
179     const ByteString& bsType) const {
180   ASSERT(pResource);
181   if (!m_pObjHolder->m_pResources) {
182     m_pObjHolder->m_pResources.Reset(
183         m_pDocument->NewIndirect<CPDF_Dictionary>());
184     m_pObjHolder->GetDict()->SetNewFor<CPDF_Reference>(
185         "Resources", m_pDocument.Get(),
186         m_pObjHolder->m_pResources->GetObjNum());
187   }
188   CPDF_Dictionary* pResList = m_pObjHolder->m_pResources->GetDictFor(bsType);
189   if (!pResList)
190     pResList = m_pObjHolder->m_pResources->SetNewFor<CPDF_Dictionary>(bsType);
191 
192   ByteString name;
193   int idnum = 1;
194   while (1) {
195     name = ByteString::Format("FX%c%d", bsType[0], idnum);
196     if (!pResList->KeyExist(name))
197       break;
198 
199     idnum++;
200   }
201   pResList->SetNewFor<CPDF_Reference>(name, m_pDocument.Get(),
202                                       pResource->GetObjNum());
203   return name;
204 }
205 
ProcessPageObjects(std::ostringstream * buf)206 bool CPDF_PageContentGenerator::ProcessPageObjects(std::ostringstream* buf) {
207   bool bDirty = false;
208   std::unique_ptr<const CPDF_ContentMarks> empty_content_marks =
209       pdfium::MakeUnique<CPDF_ContentMarks>();
210   const CPDF_ContentMarks* content_marks = empty_content_marks.get();
211 
212   for (auto& pPageObj : m_pageObjects) {
213     if (m_pObjHolder->IsPage() && !pPageObj->IsDirty())
214       continue;
215 
216     bDirty = true;
217     content_marks = ProcessContentMarks(buf, pPageObj.Get(), content_marks);
218     ProcessPageObject(buf, pPageObj.Get());
219   }
220   FinishMarks(buf, content_marks);
221   return bDirty;
222 }
223 
UpdateStreamlessPageObjects(int new_content_stream_index)224 void CPDF_PageContentGenerator::UpdateStreamlessPageObjects(
225     int new_content_stream_index) {
226   for (auto& pPageObj : m_pageObjects) {
227     if (pPageObj->GetContentStream() == CPDF_PageObject::kNoContentStream)
228       pPageObj->SetContentStream(new_content_stream_index);
229   }
230 }
231 
ProcessContentMarks(std::ostringstream * buf,const CPDF_PageObject * pPageObj,const CPDF_ContentMarks * pPrev)232 const CPDF_ContentMarks* CPDF_PageContentGenerator::ProcessContentMarks(
233     std::ostringstream* buf,
234     const CPDF_PageObject* pPageObj,
235     const CPDF_ContentMarks* pPrev) {
236   const CPDF_ContentMarks* pNext = &pPageObj->m_ContentMarks;
237 
238   size_t first_different = pPrev->FindFirstDifference(pNext);
239 
240   // Close all marks that are in prev but not in next.
241   // Technically we should iterate backwards to close from the top to the
242   // bottom, but since the EMC operators do not identify which mark they are
243   // closing, it does not matter.
244   for (size_t i = first_different; i < pPrev->CountItems(); ++i)
245     *buf << "EMC\n";
246 
247   // Open all marks that are in next but not in prev.
248   for (size_t i = first_different; i < pNext->CountItems(); ++i) {
249     const CPDF_ContentMarkItem* item = pNext->GetItem(i);
250 
251     // Write mark tag.
252     *buf << "/" << PDF_NameEncode(item->GetName()) << " ";
253 
254     // If there are no parameters, write a BMC (begin marked content) operator.
255     if (item->GetParamType() == CPDF_ContentMarkItem::kNone) {
256       *buf << "BMC\n";
257       continue;
258     }
259 
260     // If there are parameters, write properties, direct or indirect.
261     switch (item->GetParamType()) {
262       case CPDF_ContentMarkItem::kDirectDict: {
263         CPDF_StringArchiveStream archive_stream(buf);
264         item->GetParam()->WriteTo(&archive_stream, nullptr);
265         *buf << " ";
266         break;
267       }
268       case CPDF_ContentMarkItem::kPropertiesDict: {
269         *buf << "/" << item->GetPropertyName() << " ";
270         break;
271       }
272       default:
273         NOTREACHED();
274         break;
275     }
276 
277     // Write BDC (begin dictionary content) operator.
278     *buf << "BDC\n";
279   }
280 
281   return pNext;
282 }
283 
FinishMarks(std::ostringstream * buf,const CPDF_ContentMarks * pContentMarks)284 void CPDF_PageContentGenerator::FinishMarks(
285     std::ostringstream* buf,
286     const CPDF_ContentMarks* pContentMarks) {
287   // Technically we should iterate backwards to close from the top to the
288   // bottom, but since the EMC operators do not identify which mark they are
289   // closing, it does not matter.
290   for (size_t i = 0; i < pContentMarks->CountItems(); ++i)
291     *buf << "EMC\n";
292 }
293 
ProcessPageObject(std::ostringstream * buf,CPDF_PageObject * pPageObj)294 void CPDF_PageContentGenerator::ProcessPageObject(std::ostringstream* buf,
295                                                   CPDF_PageObject* pPageObj) {
296   if (CPDF_ImageObject* pImageObject = pPageObj->AsImage())
297     ProcessImage(buf, pImageObject);
298   else if (CPDF_PathObject* pPathObj = pPageObj->AsPath())
299     ProcessPath(buf, pPathObj);
300   else if (CPDF_TextObject* pTextObj = pPageObj->AsText())
301     ProcessText(buf, pTextObj);
302   pPageObj->SetDirty(false);
303 }
304 
ProcessImage(std::ostringstream * buf,CPDF_ImageObject * pImageObj)305 void CPDF_PageContentGenerator::ProcessImage(std::ostringstream* buf,
306                                              CPDF_ImageObject* pImageObj) {
307   if ((pImageObj->matrix().a == 0 && pImageObj->matrix().b == 0) ||
308       (pImageObj->matrix().c == 0 && pImageObj->matrix().d == 0)) {
309     return;
310   }
311   *buf << "q " << pImageObj->matrix() << " cm ";
312 
313   RetainPtr<CPDF_Image> pImage = pImageObj->GetImage();
314   if (pImage->IsInline())
315     return;
316 
317   CPDF_Stream* pStream = pImage->GetStream();
318   if (!pStream)
319     return;
320 
321   bool bWasInline = pStream->IsInline();
322   if (bWasInline)
323     pImage->ConvertStreamToIndirectObject();
324 
325   ByteString name = RealizeResource(pStream, "XObject");
326   if (bWasInline) {
327     auto* pPageData = CPDF_DocPageData::FromDocument(m_pDocument.Get());
328     pImageObj->SetImage(pPageData->GetImage(pStream->GetObjNum()));
329   }
330 
331   *buf << "/" << PDF_NameEncode(name) << " Do Q\n";
332 }
333 
334 // Processing path with operators from Tables 4.9 and 4.10 of PDF spec 1.7:
335 // "re" appends a rectangle (here, used only if the whole path is a rectangle)
336 // "m" moves current point to the given coordinates
337 // "l" creates a line from current point to the new point
338 // "c" adds a Bezier curve from current to last point, using the two other
339 // points as the Bezier control points
340 // Note: "l", "c" change the current point
341 // "h" closes the subpath (appends a line from current to starting point)
342 // Path painting operators: "S", "n", "B", "f", "B*", "f*", depending on
343 // the filling mode and whether we want stroking the path or not.
344 // "Q" restores the graphics state imposed by the ProcessGraphics method.
ProcessPath(std::ostringstream * buf,CPDF_PathObject * pPathObj)345 void CPDF_PageContentGenerator::ProcessPath(std::ostringstream* buf,
346                                             CPDF_PathObject* pPathObj) {
347   ProcessGraphics(buf, pPathObj);
348 
349   *buf << pPathObj->matrix() << " cm ";
350 
351   const auto& pPoints = pPathObj->path().GetPoints();
352   if (pPathObj->path().IsRect()) {
353     CFX_PointF diff = pPoints[2].m_Point - pPoints[0].m_Point;
354     *buf << pPoints[0].m_Point << " " << diff << " re";
355   } else {
356     for (size_t i = 0; i < pPoints.size(); i++) {
357       if (i > 0)
358         *buf << " ";
359 
360       *buf << pPoints[i].m_Point;
361 
362       FXPT_TYPE pointType = pPoints[i].m_Type;
363       if (pointType == FXPT_TYPE::MoveTo) {
364         *buf << " m";
365       } else if (pointType == FXPT_TYPE::LineTo) {
366         *buf << " l";
367       } else if (pointType == FXPT_TYPE::BezierTo) {
368         if (i + 2 >= pPoints.size() ||
369             !pPoints[i].IsTypeAndOpen(FXPT_TYPE::BezierTo) ||
370             !pPoints[i + 1].IsTypeAndOpen(FXPT_TYPE::BezierTo) ||
371             pPoints[i + 2].m_Type != FXPT_TYPE::BezierTo) {
372           // If format is not supported, close the path and paint
373           *buf << " h";
374           break;
375         }
376         *buf << " ";
377         *buf << pPoints[i + 1].m_Point << " ";
378         *buf << pPoints[i + 2].m_Point << " c";
379         i += 2;
380       }
381       if (pPoints[i].m_CloseFigure)
382         *buf << " h";
383     }
384   }
385   if (pPathObj->has_no_filltype())
386     *buf << (pPathObj->stroke() ? " S" : " n");
387   else if (pPathObj->has_winding_filltype())
388     *buf << (pPathObj->stroke() ? " B" : " f");
389   else if (pPathObj->has_alternate_filltype())
390     *buf << (pPathObj->stroke() ? " B*" : " f*");
391   *buf << " Q\n";
392 }
393 
394 // This method supports color operators rg and RGB from Table 4.24 of PDF spec
395 // 1.7. A color will not be set if the colorspace is not DefaultRGB or the RGB
396 // values cannot be obtained. The method also adds an external graphics
397 // dictionary, as described in Section 4.3.4.
398 // "rg" sets the fill color, "RG" sets the stroke color (using DefaultRGB)
399 // "w" sets the stroke line width.
400 // "ca" sets the fill alpha, "CA" sets the stroke alpha.
401 // "q" saves the graphics state, so that the settings can later be reversed
ProcessGraphics(std::ostringstream * buf,CPDF_PageObject * pPageObj)402 void CPDF_PageContentGenerator::ProcessGraphics(std::ostringstream* buf,
403                                                 CPDF_PageObject* pPageObj) {
404   *buf << "q ";
405   float fillColor[3];
406   if (GetColor(pPageObj->m_ColorState.GetFillColor(), fillColor)) {
407     *buf << fillColor[0] << " " << fillColor[1] << " " << fillColor[2]
408          << " rg ";
409   }
410   float strokeColor[3];
411   if (GetColor(pPageObj->m_ColorState.GetStrokeColor(), strokeColor)) {
412     *buf << strokeColor[0] << " " << strokeColor[1] << " " << strokeColor[2]
413          << " RG ";
414   }
415   float lineWidth = pPageObj->m_GraphState.GetLineWidth();
416   if (lineWidth != 1.0f)
417     WriteFloat(*buf, lineWidth) << " w ";
418   CFX_GraphStateData::LineCap lineCap = pPageObj->m_GraphState.GetLineCap();
419   if (lineCap != CFX_GraphStateData::LineCapButt)
420     *buf << static_cast<int>(lineCap) << " J ";
421   CFX_GraphStateData::LineJoin lineJoin = pPageObj->m_GraphState.GetLineJoin();
422   if (lineJoin != CFX_GraphStateData::LineJoinMiter)
423     *buf << static_cast<int>(lineJoin) << " j ";
424 
425   GraphicsData graphD;
426   graphD.fillAlpha = pPageObj->m_GeneralState.GetFillAlpha();
427   graphD.strokeAlpha = pPageObj->m_GeneralState.GetStrokeAlpha();
428   graphD.blendType = pPageObj->m_GeneralState.GetBlendType();
429   if (graphD.fillAlpha == 1.0f && graphD.strokeAlpha == 1.0f &&
430       graphD.blendType == BlendMode::kNormal) {
431     return;
432   }
433 
434   ByteString name;
435   auto it = m_pObjHolder->m_GraphicsMap.find(graphD);
436   if (it != m_pObjHolder->m_GraphicsMap.end()) {
437     name = it->second;
438   } else {
439     auto gsDict = pdfium::MakeRetain<CPDF_Dictionary>();
440     if (graphD.fillAlpha != 1.0f)
441       gsDict->SetNewFor<CPDF_Number>("ca", graphD.fillAlpha);
442 
443     if (graphD.strokeAlpha != 1.0f)
444       gsDict->SetNewFor<CPDF_Number>("CA", graphD.strokeAlpha);
445 
446     if (graphD.blendType != BlendMode::kNormal) {
447       gsDict->SetNewFor<CPDF_Name>("BM",
448                                    pPageObj->m_GeneralState.GetBlendMode());
449     }
450     CPDF_Object* pDict = m_pDocument->AddIndirectObject(gsDict);
451     name = RealizeResource(pDict, "ExtGState");
452     m_pObjHolder->m_GraphicsMap[graphD] = name;
453   }
454   *buf << "/" << PDF_NameEncode(name) << " gs ";
455 }
456 
ProcessDefaultGraphics(std::ostringstream * buf)457 void CPDF_PageContentGenerator::ProcessDefaultGraphics(
458     std::ostringstream* buf) {
459   *buf << "0 0 0 RG 0 0 0 rg 1 w "
460        << static_cast<int>(CFX_GraphStateData::LineCapButt) << " J "
461        << static_cast<int>(CFX_GraphStateData::LineJoinMiter) << " j\n";
462   ByteString name = GetOrCreateDefaultGraphics();
463   *buf << "/" << PDF_NameEncode(name) << " gs ";
464 }
465 
GetOrCreateDefaultGraphics() const466 ByteString CPDF_PageContentGenerator::GetOrCreateDefaultGraphics() const {
467   GraphicsData defaultGraphics;
468   defaultGraphics.fillAlpha = 1.0f;
469   defaultGraphics.strokeAlpha = 1.0f;
470   defaultGraphics.blendType = BlendMode::kNormal;
471   auto it = m_pObjHolder->m_GraphicsMap.find(defaultGraphics);
472 
473   // If default graphics already exists, return it.
474   if (it != m_pObjHolder->m_GraphicsMap.end())
475     return it->second;
476 
477   // Otherwise, create them.
478   auto gsDict = pdfium::MakeRetain<CPDF_Dictionary>();
479   gsDict->SetNewFor<CPDF_Number>("ca", defaultGraphics.fillAlpha);
480   gsDict->SetNewFor<CPDF_Number>("CA", defaultGraphics.strokeAlpha);
481   gsDict->SetNewFor<CPDF_Name>("BM", "Normal");
482   CPDF_Object* pDict = m_pDocument->AddIndirectObject(gsDict);
483   ByteString name = RealizeResource(pDict, "ExtGState");
484   m_pObjHolder->m_GraphicsMap[defaultGraphics] = name;
485   return name;
486 }
487 
488 // This method adds text to the buffer, BT begins the text object, ET ends it.
489 // Tm sets the text matrix (allows positioning and transforming text).
490 // Tf sets the font name (from Font in Resources) and font size.
491 // Tj sets the actual text, <####...> is used when specifying charcodes.
ProcessText(std::ostringstream * buf,CPDF_TextObject * pTextObj)492 void CPDF_PageContentGenerator::ProcessText(std::ostringstream* buf,
493                                             CPDF_TextObject* pTextObj) {
494   ProcessGraphics(buf, pTextObj);
495   *buf << "BT " << pTextObj->GetTextMatrix() << " Tm ";
496   RetainPtr<CPDF_Font> pFont(pTextObj->GetFont());
497   if (!pFont)
498     pFont = CPDF_Font::GetStockFont(m_pDocument.Get(), "Helvetica");
499 
500   FontData data;
501   const CPDF_FontEncoding* pEncoding = nullptr;
502   if (pFont->IsType1Font()) {
503     data.type = "Type1";
504     pEncoding = pFont->AsType1Font()->GetEncoding();
505   } else if (pFont->IsTrueTypeFont()) {
506     data.type = "TrueType";
507     pEncoding = pFont->AsTrueTypeFont()->GetEncoding();
508   } else if (pFont->IsCIDFont()) {
509     data.type = "Type0";
510   } else {
511     return;
512   }
513   data.baseFont = pFont->GetBaseFontName();
514   auto it = m_pObjHolder->m_FontsMap.find(data);
515   ByteString dictName;
516   if (it != m_pObjHolder->m_FontsMap.end()) {
517     dictName = it->second;
518   } else {
519     CPDF_Object* pIndirectFont = pFont->GetFontDict();
520     if (pIndirectFont->IsInline()) {
521       // In this case we assume it must be a standard font
522       auto pFontDict = pdfium::MakeRetain<CPDF_Dictionary>();
523       pFontDict->SetNewFor<CPDF_Name>("Type", "Font");
524       pFontDict->SetNewFor<CPDF_Name>("Subtype", data.type);
525       pFontDict->SetNewFor<CPDF_Name>("BaseFont", data.baseFont);
526       if (pEncoding) {
527         pFontDict->SetFor("Encoding",
528                           pEncoding->Realize(m_pDocument->GetByteStringPool()));
529       }
530       pIndirectFont = m_pDocument->AddIndirectObject(pFontDict);
531     }
532     dictName = RealizeResource(pIndirectFont, "Font");
533     m_pObjHolder->m_FontsMap[data] = dictName;
534   }
535   *buf << "/" << PDF_NameEncode(dictName) << " ";
536   WriteFloat(*buf, pTextObj->GetFontSize()) << " Tf ";
537   ByteString text;
538   for (uint32_t charcode : pTextObj->GetCharCodes()) {
539     if (charcode != CPDF_Font::kInvalidCharCode)
540       pFont->AppendChar(&text, charcode);
541   }
542   *buf << PDF_EncodeString(text, true) << " Tj ET";
543   *buf << " Q\n";
544 }
545