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_dictionary.h" 21 #include "core/fpdfapi/parser/cpdf_document.h" 22 #include "core/fpdfapi/parser/cpdf_name.h" 23 #include "core/fpdfapi/parser/cpdf_number.h" 24 #include "core/fpdfapi/parser/cpdf_reference.h" 25 #include "core/fpdfapi/parser/cpdf_stream.h" 26 #include "core/fpdfapi/parser/fpdf_parser_decode.h" 27 28 namespace { 29 30 CFX_ByteTextBuf& operator<<(CFX_ByteTextBuf& ar, const CFX_Matrix& matrix) { 31 ar << matrix.a << " " << matrix.b << " " << matrix.c << " " << matrix.d << " " 32 << matrix.e << " " << matrix.f; 33 return ar; 34 } 35 36 bool GetColor(const CPDF_Color* pColor, FX_FLOAT* rgb) { 37 int intRGB[3]; 38 if (!pColor || 39 pColor->GetColorSpace() != CPDF_ColorSpace::GetStockCS(PDFCS_DEVICERGB) || 40 !pColor->GetRGB(intRGB[0], intRGB[1], intRGB[2])) { 41 return false; 42 } 43 rgb[0] = intRGB[0] / 255.0f; 44 rgb[1] = intRGB[1] / 255.0f; 45 rgb[2] = intRGB[2] / 255.0f; 46 return true; 47 } 48 49 } // namespace 50 51 CPDF_PageContentGenerator::CPDF_PageContentGenerator(CPDF_Page* pPage) 52 : m_pPage(pPage), m_pDocument(m_pPage->m_pDocument) { 53 for (const auto& pObj : *pPage->GetPageObjectList()) { 54 if (pObj) 55 m_pageObjects.push_back(pObj.get()); 56 } 57 } 58 59 CPDF_PageContentGenerator::~CPDF_PageContentGenerator() {} 60 61 void CPDF_PageContentGenerator::GenerateContent() { 62 CFX_ByteTextBuf buf; 63 for (CPDF_PageObject* pPageObj : m_pageObjects) { 64 if (CPDF_ImageObject* pImageObject = pPageObj->AsImage()) 65 ProcessImage(&buf, pImageObject); 66 else if (CPDF_PathObject* pPathObj = pPageObj->AsPath()) 67 ProcessPath(&buf, pPathObj); 68 else if (CPDF_TextObject* pTextObj = pPageObj->AsText()) 69 ProcessText(&buf, pTextObj); 70 } 71 CPDF_Dictionary* pPageDict = m_pPage->m_pFormDict; 72 CPDF_Object* pContent = 73 pPageDict ? pPageDict->GetDirectObjectFor("Contents") : nullptr; 74 if (pContent) 75 pPageDict->RemoveFor("Contents"); 76 77 CPDF_Stream* pStream = m_pDocument->NewIndirect<CPDF_Stream>(); 78 pStream->SetData(buf.GetBuffer(), buf.GetLength()); 79 pPageDict->SetNewFor<CPDF_Reference>("Contents", m_pDocument, 80 pStream->GetObjNum()); 81 } 82 83 CFX_ByteString CPDF_PageContentGenerator::RealizeResource( 84 uint32_t dwResourceObjNum, 85 const CFX_ByteString& bsType) { 86 ASSERT(dwResourceObjNum); 87 if (!m_pPage->m_pResources) { 88 m_pPage->m_pResources = m_pDocument->NewIndirect<CPDF_Dictionary>(); 89 m_pPage->m_pFormDict->SetNewFor<CPDF_Reference>( 90 "Resources", m_pDocument, m_pPage->m_pResources->GetObjNum()); 91 } 92 CPDF_Dictionary* pResList = m_pPage->m_pResources->GetDictFor(bsType); 93 if (!pResList) 94 pResList = m_pPage->m_pResources->SetNewFor<CPDF_Dictionary>(bsType); 95 96 CFX_ByteString name; 97 int idnum = 1; 98 while (1) { 99 name.Format("FX%c%d", bsType[0], idnum); 100 if (!pResList->KeyExist(name)) { 101 break; 102 } 103 idnum++; 104 } 105 pResList->SetNewFor<CPDF_Reference>(name, m_pDocument, dwResourceObjNum); 106 return name; 107 } 108 109 void CPDF_PageContentGenerator::ProcessImage(CFX_ByteTextBuf* buf, 110 CPDF_ImageObject* pImageObj) { 111 if ((pImageObj->matrix().a == 0 && pImageObj->matrix().b == 0) || 112 (pImageObj->matrix().c == 0 && pImageObj->matrix().d == 0)) { 113 return; 114 } 115 *buf << "q " << pImageObj->matrix() << " cm "; 116 117 CPDF_Image* pImage = pImageObj->GetImage(); 118 if (pImage->IsInline()) 119 return; 120 121 CPDF_Stream* pStream = pImage->GetStream(); 122 if (!pStream) 123 return; 124 125 bool bWasInline = pStream->IsInline(); 126 if (bWasInline) 127 pImage->ConvertStreamToIndirectObject(); 128 129 uint32_t dwObjNum = pStream->GetObjNum(); 130 CFX_ByteString name = RealizeResource(dwObjNum, "XObject"); 131 if (bWasInline) 132 pImageObj->SetUnownedImage(m_pDocument->GetPageData()->GetImage(dwObjNum)); 133 134 *buf << "/" << PDF_NameEncode(name) << " Do Q\n"; 135 } 136 137 // Processing path with operators from Tables 4.9 and 4.10 of PDF spec 1.7: 138 // "re" appends a rectangle (here, used only if the whole path is a rectangle) 139 // "m" moves current point to the given coordinates 140 // "l" creates a line from current point to the new point 141 // "c" adds a Bezier curve from current to last point, using the two other 142 // points as the Bezier control points 143 // Note: "l", "c" change the current point 144 // "h" closes the subpath (appends a line from current to starting point) 145 // Path painting operators: "S", "n", "B", "f", "B*", "f*", depending on 146 // the filling mode and whether we want stroking the path or not. 147 // "Q" restores the graphics state imposed by the ProcessGraphics method. 148 void CPDF_PageContentGenerator::ProcessPath(CFX_ByteTextBuf* buf, 149 CPDF_PathObject* pPathObj) { 150 ProcessGraphics(buf, pPathObj); 151 auto& pPoints = pPathObj->m_Path.GetPoints(); 152 if (pPathObj->m_Path.IsRect()) { 153 CFX_PointF diff = pPoints[2].m_Point - pPoints[0].m_Point; 154 *buf << pPoints[0].m_Point.x << " " << pPoints[0].m_Point.y << " " << diff.x 155 << " " << diff.y << " re"; 156 } else { 157 for (size_t i = 0; i < pPoints.size(); i++) { 158 if (i > 0) 159 *buf << " "; 160 *buf << pPoints[i].m_Point.x << " " << pPoints[i].m_Point.y; 161 FXPT_TYPE pointType = pPoints[i].m_Type; 162 if (pointType == FXPT_TYPE::MoveTo) { 163 *buf << " m"; 164 } else if (pointType == FXPT_TYPE::LineTo) { 165 *buf << " l"; 166 } else if (pointType == FXPT_TYPE::BezierTo) { 167 if (i + 2 >= pPoints.size() || 168 !pPoints[i].IsTypeAndOpen(FXPT_TYPE::BezierTo) || 169 !pPoints[i + 1].IsTypeAndOpen(FXPT_TYPE::BezierTo) || 170 pPoints[i + 2].m_Type != FXPT_TYPE::BezierTo) { 171 // If format is not supported, close the path and paint 172 *buf << " h"; 173 break; 174 } 175 *buf << " " << pPoints[i + 1].m_Point.x << " " 176 << pPoints[i + 1].m_Point.y << " " << pPoints[i + 2].m_Point.x 177 << " " << pPoints[i + 2].m_Point.y << " c"; 178 i += 2; 179 } 180 if (pPoints[i].m_CloseFigure) 181 *buf << " h"; 182 } 183 } 184 if (pPathObj->m_FillType == 0) 185 *buf << (pPathObj->m_bStroke ? " S" : " n"); 186 else if (pPathObj->m_FillType == FXFILL_WINDING) 187 *buf << (pPathObj->m_bStroke ? " B" : " f"); 188 else if (pPathObj->m_FillType == FXFILL_ALTERNATE) 189 *buf << (pPathObj->m_bStroke ? " B*" : " f*"); 190 *buf << " Q\n"; 191 } 192 193 // This method supports color operators rg and RGB from Table 4.24 of PDF spec 194 // 1.7. A color will not be set if the colorspace is not DefaultRGB or the RGB 195 // values cannot be obtained. The method also adds an external graphics 196 // dictionary, as described in Section 4.3.4. 197 // "rg" sets the fill color, "RG" sets the stroke color (using DefaultRGB) 198 // "w" sets the stroke line width. 199 // "ca" sets the fill alpha, "CA" sets the stroke alpha. 200 // "q" saves the graphics state, so that the settings can later be reversed 201 void CPDF_PageContentGenerator::ProcessGraphics(CFX_ByteTextBuf* buf, 202 CPDF_PageObject* pPageObj) { 203 *buf << "q "; 204 FX_FLOAT fillColor[3]; 205 if (GetColor(pPageObj->m_ColorState.GetFillColor(), fillColor)) { 206 *buf << fillColor[0] << " " << fillColor[1] << " " << fillColor[2] 207 << " rg "; 208 } 209 FX_FLOAT strokeColor[3]; 210 if (GetColor(pPageObj->m_ColorState.GetStrokeColor(), strokeColor)) { 211 *buf << strokeColor[0] << " " << strokeColor[1] << " " << strokeColor[2] 212 << " RG "; 213 } 214 FX_FLOAT lineWidth = pPageObj->m_GraphState.GetLineWidth(); 215 if (lineWidth != 1.0f) 216 *buf << lineWidth << " w "; 217 218 GraphicsData graphD; 219 graphD.fillAlpha = pPageObj->m_GeneralState.GetFillAlpha(); 220 graphD.strokeAlpha = pPageObj->m_GeneralState.GetStrokeAlpha(); 221 if (graphD.fillAlpha == 1.0f && graphD.strokeAlpha == 1.0f) 222 return; 223 224 CFX_ByteString name; 225 auto it = m_pPage->m_GraphicsMap.find(graphD); 226 if (it != m_pPage->m_GraphicsMap.end()) { 227 name = it->second; 228 } else { 229 auto gsDict = pdfium::MakeUnique<CPDF_Dictionary>(); 230 gsDict->SetNewFor<CPDF_Number>("ca", graphD.fillAlpha); 231 gsDict->SetNewFor<CPDF_Number>("CA", graphD.strokeAlpha); 232 CPDF_Object* pDict = m_pDocument->AddIndirectObject(std::move(gsDict)); 233 uint32_t dwObjNum = pDict->GetObjNum(); 234 name = RealizeResource(dwObjNum, "ExtGState"); 235 m_pPage->m_GraphicsMap[graphD] = name; 236 } 237 *buf << "/" << PDF_NameEncode(name) << " gs "; 238 } 239 240 // This method adds text to the buffer, BT begins the text object, ET ends it. 241 // Tm sets the text matrix (allows positioning and transforming text). 242 // Tf sets the font name (from Font in Resources) and font size. 243 // Tj sets the actual text, <####...> is used when specifying charcodes. 244 void CPDF_PageContentGenerator::ProcessText(CFX_ByteTextBuf* buf, 245 CPDF_TextObject* pTextObj) { 246 // TODO(npm): Add support for something other than standard type1 fonts. 247 *buf << "BT " << pTextObj->GetTextMatrix() << " Tm "; 248 CPDF_Font* pFont = pTextObj->GetFont(); 249 if (!pFont) 250 pFont = CPDF_Font::GetStockFont(m_pDocument, "Helvetica"); 251 FontData fontD; 252 fontD.baseFont = pFont->GetBaseFont(); 253 auto it = m_pPage->m_FontsMap.find(fontD); 254 CFX_ByteString dictName; 255 if (it != m_pPage->m_FontsMap.end()) { 256 dictName = it->second; 257 } else { 258 auto fontDict = pdfium::MakeUnique<CPDF_Dictionary>(); 259 fontDict->SetNewFor<CPDF_Name>("Type", "Font"); 260 fontDict->SetNewFor<CPDF_Name>("Subtype", "Type1"); 261 fontDict->SetNewFor<CPDF_Name>("BaseFont", fontD.baseFont); 262 CPDF_Object* pDict = m_pDocument->AddIndirectObject(std::move(fontDict)); 263 uint32_t dwObjNum = pDict->GetObjNum(); 264 dictName = RealizeResource(dwObjNum, "Font"); 265 m_pPage->m_FontsMap[fontD] = dictName; 266 } 267 *buf << "/" << PDF_NameEncode(dictName) << " " << pTextObj->GetFontSize() 268 << " Tf "; 269 CFX_ByteString text; 270 for (uint32_t charcode : pTextObj->m_CharCodes) { 271 if (charcode == CPDF_Font::kInvalidCharCode) 272 continue; 273 pFont->AppendChar(text, charcode); 274 } 275 *buf << PDF_EncodeString(text, true) << " Tj ET\n"; 276 } 277