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 <tuple> 10#include <utility> 11 12#include "core/fpdfapi/font/cpdf_font.h" 13#include "core/fpdfapi/page/cpdf_docpagedata.h" 14#include "core/fpdfapi/page/cpdf_image.h" 15#include "core/fpdfapi/page/cpdf_imageobject.h" 16#include "core/fpdfapi/page/cpdf_page.h" 17#include "core/fpdfapi/page/cpdf_path.h" 18#include "core/fpdfapi/page/cpdf_pathobject.h" 19#include "core/fpdfapi/page/cpdf_textobject.h" 20#include "core/fpdfapi/parser/cpdf_array.h" 21#include "core/fpdfapi/parser/cpdf_dictionary.h" 22#include "core/fpdfapi/parser/cpdf_document.h" 23#include "core/fpdfapi/parser/cpdf_name.h" 24#include "core/fpdfapi/parser/cpdf_number.h" 25#include "core/fpdfapi/parser/cpdf_reference.h" 26#include "core/fpdfapi/parser/cpdf_stream.h" 27#include "core/fpdfapi/parser/fpdf_parser_decode.h" 28 29namespace { 30 31std::ostream& operator<<(std::ostream& ar, const CFX_Matrix& matrix) { 32 ar << matrix.a << " " << matrix.b << " " << matrix.c << " " << matrix.d << " " 33 << matrix.e << " " << matrix.f; 34 return ar; 35} 36 37bool GetColor(const CPDF_Color* pColor, float* rgb) { 38 int intRGB[3]; 39 if (!pColor || 40 pColor->GetColorSpace() != CPDF_ColorSpace::GetStockCS(PDFCS_DEVICERGB) || 41 !pColor->GetRGB(&intRGB[0], &intRGB[1], &intRGB[2])) { 42 return false; 43 } 44 rgb[0] = intRGB[0] / 255.0f; 45 rgb[1] = intRGB[1] / 255.0f; 46 rgb[2] = intRGB[2] / 255.0f; 47 return true; 48} 49 50} // namespace 51 52CPDF_PageContentGenerator::CPDF_PageContentGenerator( 53 CPDF_PageObjectHolder* pObjHolder) 54 : m_pObjHolder(pObjHolder), m_pDocument(pObjHolder->m_pDocument.Get()) { 55 for (const auto& pObj : *pObjHolder->GetPageObjectList()) { 56 if (pObj) 57 m_pageObjects.emplace_back(pObj.get()); 58 } 59} 60 61CPDF_PageContentGenerator::~CPDF_PageContentGenerator() {} 62 63void CPDF_PageContentGenerator::GenerateContent() { 64 ASSERT(m_pObjHolder->IsPage()); 65 66 CPDF_Document* pDoc = m_pDocument.Get(); 67 std::ostringstream buf; 68 69 // Set the default graphic state values 70 buf << "q\n"; 71 if (!m_pObjHolder->GetLastCTM().IsIdentity()) 72 buf << m_pObjHolder->GetLastCTM().GetInverse() << " cm\n"; 73 ProcessDefaultGraphics(&buf); 74 75 // Process the page objects 76 if (!ProcessPageObjects(&buf)) 77 return; 78 79 // Return graphics to original state 80 buf << "Q\n"; 81 82 // Add buffer to a stream in page's 'Contents' 83 CPDF_Dictionary* pPageDict = m_pObjHolder->m_pFormDict.Get(); 84 CPDF_Object* pContent = 85 pPageDict ? pPageDict->GetObjectFor("Contents") : nullptr; 86 CPDF_Stream* pStream = pDoc->NewIndirect<CPDF_Stream>(); 87 pStream->SetData(&buf); 88 if (pContent) { 89 CPDF_Array* pArray = ToArray(pContent); 90 if (pArray) { 91 pArray->AddNew<CPDF_Reference>(pDoc, pStream->GetObjNum()); 92 return; 93 } 94 CPDF_Reference* pReference = ToReference(pContent); 95 if (!pReference) { 96 pPageDict->SetNewFor<CPDF_Reference>("Contents", m_pDocument.Get(), 97 pStream->GetObjNum()); 98 return; 99 } 100 CPDF_Object* pDirectObj = pReference->GetDirect(); 101 if (!pDirectObj) { 102 pPageDict->SetNewFor<CPDF_Reference>("Contents", m_pDocument.Get(), 103 pStream->GetObjNum()); 104 return; 105 } 106 CPDF_Array* pObjArray = pDirectObj->AsArray(); 107 if (pObjArray) { 108 pObjArray->AddNew<CPDF_Reference>(pDoc, pStream->GetObjNum()); 109 return; 110 } 111 if (pDirectObj->IsStream()) { 112 CPDF_Array* pContentArray = pDoc->NewIndirect<CPDF_Array>(); 113 pContentArray->AddNew<CPDF_Reference>(pDoc, pDirectObj->GetObjNum()); 114 pContentArray->AddNew<CPDF_Reference>(pDoc, pStream->GetObjNum()); 115 pPageDict->SetNewFor<CPDF_Reference>("Contents", pDoc, 116 pContentArray->GetObjNum()); 117 return; 118 } 119 } 120 pPageDict->SetNewFor<CPDF_Reference>("Contents", m_pDocument.Get(), 121 pStream->GetObjNum()); 122} 123 124ByteString CPDF_PageContentGenerator::RealizeResource( 125 uint32_t dwResourceObjNum, 126 const ByteString& bsType) { 127 ASSERT(dwResourceObjNum); 128 if (!m_pObjHolder->m_pResources) { 129 m_pObjHolder->m_pResources = m_pDocument->NewIndirect<CPDF_Dictionary>(); 130 m_pObjHolder->m_pFormDict->SetNewFor<CPDF_Reference>( 131 "Resources", m_pDocument.Get(), 132 m_pObjHolder->m_pResources->GetObjNum()); 133 } 134 CPDF_Dictionary* pResList = m_pObjHolder->m_pResources->GetDictFor(bsType); 135 if (!pResList) 136 pResList = m_pObjHolder->m_pResources->SetNewFor<CPDF_Dictionary>(bsType); 137 138 ByteString name; 139 int idnum = 1; 140 while (1) { 141 name = ByteString::Format("FX%c%d", bsType[0], idnum); 142 if (!pResList->KeyExist(name)) 143 break; 144 145 idnum++; 146 } 147 pResList->SetNewFor<CPDF_Reference>(name, m_pDocument.Get(), 148 dwResourceObjNum); 149 return name; 150} 151 152bool CPDF_PageContentGenerator::ProcessPageObjects(std::ostringstream* buf) { 153 bool bDirty = false; 154 for (auto& pPageObj : m_pageObjects) { 155 if (m_pObjHolder->IsPage() && !pPageObj->IsDirty()) 156 continue; 157 158 bDirty = true; 159 if (CPDF_ImageObject* pImageObject = pPageObj->AsImage()) 160 ProcessImage(buf, pImageObject); 161 else if (CPDF_PathObject* pPathObj = pPageObj->AsPath()) 162 ProcessPath(buf, pPathObj); 163 else if (CPDF_TextObject* pTextObj = pPageObj->AsText()) 164 ProcessText(buf, pTextObj); 165 pPageObj->SetDirty(false); 166 } 167 return bDirty; 168} 169 170void CPDF_PageContentGenerator::ProcessImage(std::ostringstream* buf, 171 CPDF_ImageObject* pImageObj) { 172 if ((pImageObj->matrix().a == 0 && pImageObj->matrix().b == 0) || 173 (pImageObj->matrix().c == 0 && pImageObj->matrix().d == 0)) { 174 return; 175 } 176 *buf << "q " << pImageObj->matrix() << " cm "; 177 178 RetainPtr<CPDF_Image> pImage = pImageObj->GetImage(); 179 if (pImage->IsInline()) 180 return; 181 182 CPDF_Stream* pStream = pImage->GetStream(); 183 if (!pStream) 184 return; 185 186 bool bWasInline = pStream->IsInline(); 187 if (bWasInline) 188 pImage->ConvertStreamToIndirectObject(); 189 190 uint32_t dwObjNum = pStream->GetObjNum(); 191 ByteString name = RealizeResource(dwObjNum, "XObject"); 192 if (bWasInline) 193 pImageObj->SetImage(m_pDocument->GetPageData()->GetImage(dwObjNum)); 194 195 *buf << "/" << PDF_NameEncode(name) << " Do Q\n"; 196} 197 198// Processing path with operators from Tables 4.9 and 4.10 of PDF spec 1.7: 199// "re" appends a rectangle (here, used only if the whole path is a rectangle) 200// "m" moves current point to the given coordinates 201// "l" creates a line from current point to the new point 202// "c" adds a Bezier curve from current to last point, using the two other 203// points as the Bezier control points 204// Note: "l", "c" change the current point 205// "h" closes the subpath (appends a line from current to starting point) 206// Path painting operators: "S", "n", "B", "f", "B*", "f*", depending on 207// the filling mode and whether we want stroking the path or not. 208// "Q" restores the graphics state imposed by the ProcessGraphics method. 209void CPDF_PageContentGenerator::ProcessPath(std::ostringstream* buf, 210 CPDF_PathObject* pPathObj) { 211 ProcessGraphics(buf, pPathObj); 212 213 *buf << pPathObj->m_Matrix << " cm "; 214 215 auto& pPoints = pPathObj->m_Path.GetPoints(); 216 if (pPathObj->m_Path.IsRect()) { 217 CFX_PointF diff = pPoints[2].m_Point - pPoints[0].m_Point; 218 *buf << pPoints[0].m_Point.x << " " << pPoints[0].m_Point.y << " " << diff.x 219 << " " << diff.y << " re"; 220 } else { 221 for (size_t i = 0; i < pPoints.size(); i++) { 222 if (i > 0) 223 *buf << " "; 224 *buf << pPoints[i].m_Point.x << " " << pPoints[i].m_Point.y; 225 FXPT_TYPE pointType = pPoints[i].m_Type; 226 if (pointType == FXPT_TYPE::MoveTo) { 227 *buf << " m"; 228 } else if (pointType == FXPT_TYPE::LineTo) { 229 *buf << " l"; 230 } else if (pointType == FXPT_TYPE::BezierTo) { 231 if (i + 2 >= pPoints.size() || 232 !pPoints[i].IsTypeAndOpen(FXPT_TYPE::BezierTo) || 233 !pPoints[i + 1].IsTypeAndOpen(FXPT_TYPE::BezierTo) || 234 pPoints[i + 2].m_Type != FXPT_TYPE::BezierTo) { 235 // If format is not supported, close the path and paint 236 *buf << " h"; 237 break; 238 } 239 *buf << " " << pPoints[i + 1].m_Point.x << " " 240 << pPoints[i + 1].m_Point.y << " " << pPoints[i + 2].m_Point.x 241 << " " << pPoints[i + 2].m_Point.y << " c"; 242 i += 2; 243 } 244 if (pPoints[i].m_CloseFigure) 245 *buf << " h"; 246 } 247 } 248 if (pPathObj->m_FillType == 0) 249 *buf << (pPathObj->m_bStroke ? " S" : " n"); 250 else if (pPathObj->m_FillType == FXFILL_WINDING) 251 *buf << (pPathObj->m_bStroke ? " B" : " f"); 252 else if (pPathObj->m_FillType == FXFILL_ALTERNATE) 253 *buf << (pPathObj->m_bStroke ? " B*" : " f*"); 254 *buf << " Q\n"; 255} 256 257// This method supports color operators rg and RGB from Table 4.24 of PDF spec 258// 1.7. A color will not be set if the colorspace is not DefaultRGB or the RGB 259// values cannot be obtained. The method also adds an external graphics 260// dictionary, as described in Section 4.3.4. 261// "rg" sets the fill color, "RG" sets the stroke color (using DefaultRGB) 262// "w" sets the stroke line width. 263// "ca" sets the fill alpha, "CA" sets the stroke alpha. 264// "q" saves the graphics state, so that the settings can later be reversed 265void CPDF_PageContentGenerator::ProcessGraphics(std::ostringstream* buf, 266 CPDF_PageObject* pPageObj) { 267 *buf << "q "; 268 float fillColor[3]; 269 if (GetColor(pPageObj->m_ColorState.GetFillColor(), fillColor)) { 270 *buf << fillColor[0] << " " << fillColor[1] << " " << fillColor[2] 271 << " rg "; 272 } 273 float strokeColor[3]; 274 if (GetColor(pPageObj->m_ColorState.GetStrokeColor(), strokeColor)) { 275 *buf << strokeColor[0] << " " << strokeColor[1] << " " << strokeColor[2] 276 << " RG "; 277 } 278 float lineWidth = pPageObj->m_GraphState.GetLineWidth(); 279 if (lineWidth != 1.0f) 280 *buf << lineWidth << " w "; 281 CFX_GraphStateData::LineCap lineCap = pPageObj->m_GraphState.GetLineCap(); 282 if (lineCap != CFX_GraphStateData::LineCapButt) 283 *buf << static_cast<int>(lineCap) << " J "; 284 CFX_GraphStateData::LineJoin lineJoin = pPageObj->m_GraphState.GetLineJoin(); 285 if (lineJoin != CFX_GraphStateData::LineJoinMiter) 286 *buf << static_cast<int>(lineJoin) << " j "; 287 288 GraphicsData graphD; 289 graphD.fillAlpha = pPageObj->m_GeneralState.GetFillAlpha(); 290 graphD.strokeAlpha = pPageObj->m_GeneralState.GetStrokeAlpha(); 291 graphD.blendType = pPageObj->m_GeneralState.GetBlendType(); 292 if (graphD.fillAlpha == 1.0f && graphD.strokeAlpha == 1.0f && 293 (graphD.blendType == FXDIB_BLEND_UNSUPPORTED || 294 graphD.blendType == FXDIB_BLEND_NORMAL)) { 295 return; 296 } 297 298 ByteString name; 299 auto it = m_pObjHolder->m_GraphicsMap.find(graphD); 300 if (it != m_pObjHolder->m_GraphicsMap.end()) { 301 name = it->second; 302 } else { 303 auto gsDict = pdfium::MakeUnique<CPDF_Dictionary>(); 304 if (graphD.fillAlpha != 1.0f) 305 gsDict->SetNewFor<CPDF_Number>("ca", graphD.fillAlpha); 306 307 if (graphD.strokeAlpha != 1.0f) 308 gsDict->SetNewFor<CPDF_Number>("CA", graphD.strokeAlpha); 309 310 if (graphD.blendType != FXDIB_BLEND_UNSUPPORTED && 311 graphD.blendType != FXDIB_BLEND_NORMAL) { 312 gsDict->SetNewFor<CPDF_Name>("BM", 313 pPageObj->m_GeneralState.GetBlendMode()); 314 } 315 CPDF_Object* pDict = m_pDocument->AddIndirectObject(std::move(gsDict)); 316 uint32_t dwObjNum = pDict->GetObjNum(); 317 name = RealizeResource(dwObjNum, "ExtGState"); 318 m_pObjHolder->m_GraphicsMap[graphD] = name; 319 } 320 *buf << "/" << PDF_NameEncode(name) << " gs "; 321} 322 323void CPDF_PageContentGenerator::ProcessDefaultGraphics( 324 std::ostringstream* buf) { 325 *buf << "0 0 0 RG 0 0 0 rg 1 w " 326 << static_cast<int>(CFX_GraphStateData::LineCapButt) << " J " 327 << static_cast<int>(CFX_GraphStateData::LineJoinMiter) << " j\n"; 328 GraphicsData defaultGraphics; 329 defaultGraphics.fillAlpha = 1.0f; 330 defaultGraphics.strokeAlpha = 1.0f; 331 defaultGraphics.blendType = FXDIB_BLEND_NORMAL; 332 auto it = m_pObjHolder->m_GraphicsMap.find(defaultGraphics); 333 ByteString name; 334 if (it != m_pObjHolder->m_GraphicsMap.end()) { 335 name = it->second; 336 } else { 337 auto gsDict = pdfium::MakeUnique<CPDF_Dictionary>(); 338 gsDict->SetNewFor<CPDF_Number>("ca", defaultGraphics.fillAlpha); 339 gsDict->SetNewFor<CPDF_Number>("CA", defaultGraphics.strokeAlpha); 340 gsDict->SetNewFor<CPDF_Name>("BM", "Normal"); 341 CPDF_Object* pDict = m_pDocument->AddIndirectObject(std::move(gsDict)); 342 uint32_t dwObjNum = pDict->GetObjNum(); 343 name = RealizeResource(dwObjNum, "ExtGState"); 344 m_pObjHolder->m_GraphicsMap[defaultGraphics] = name; 345 } 346 *buf << "/" << PDF_NameEncode(name).c_str() << " gs "; 347} 348 349// This method adds text to the buffer, BT begins the text object, ET ends it. 350// Tm sets the text matrix (allows positioning and transforming text). 351// Tf sets the font name (from Font in Resources) and font size. 352// Tj sets the actual text, <####...> is used when specifying charcodes. 353void CPDF_PageContentGenerator::ProcessText(std::ostringstream* buf, 354 CPDF_TextObject* pTextObj) { 355 ProcessGraphics(buf, pTextObj); 356 *buf << "BT " << pTextObj->GetTextMatrix() << " Tm "; 357 CPDF_Font* pFont = pTextObj->GetFont(); 358 if (!pFont) 359 pFont = CPDF_Font::GetStockFont(m_pDocument.Get(), "Helvetica"); 360 FontData fontD; 361 if (pFont->IsType1Font()) 362 fontD.type = "Type1"; 363 else if (pFont->IsTrueTypeFont()) 364 fontD.type = "TrueType"; 365 else if (pFont->IsCIDFont()) 366 fontD.type = "Type0"; 367 else 368 return; 369 fontD.baseFont = pFont->GetBaseFont(); 370 auto it = m_pObjHolder->m_FontsMap.find(fontD); 371 ByteString dictName; 372 if (it != m_pObjHolder->m_FontsMap.end()) { 373 dictName = it->second; 374 } else { 375 uint32_t dwObjNum = pFont->GetFontDict()->GetObjNum(); 376 if (!dwObjNum) { 377 // In this case we assume it must be a standard font 378 auto fontDict = pdfium::MakeUnique<CPDF_Dictionary>(); 379 fontDict->SetNewFor<CPDF_Name>("Type", "Font"); 380 fontDict->SetNewFor<CPDF_Name>("Subtype", fontD.type); 381 fontDict->SetNewFor<CPDF_Name>("BaseFont", fontD.baseFont); 382 CPDF_Object* pDict = m_pDocument->AddIndirectObject(std::move(fontDict)); 383 dwObjNum = pDict->GetObjNum(); 384 } 385 dictName = RealizeResource(dwObjNum, "Font"); 386 m_pObjHolder->m_FontsMap[fontD] = dictName; 387 } 388 *buf << "/" << PDF_NameEncode(dictName) << " " << pTextObj->GetFontSize() 389 << " Tf "; 390 ByteString text; 391 for (uint32_t charcode : pTextObj->GetCharCodes()) { 392 if (charcode != CPDF_Font::kInvalidCharCode) 393 pFont->AppendChar(&text, charcode); 394 } 395 *buf << PDF_EncodeString(text, true) << " Tj ET"; 396 *buf << " Q\n"; 397} 398