Home | History | Annotate | Download | only in edit
      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 
     29 namespace {
     30 
     31 std::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 
     37 bool 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 
     52 CPDF_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 
     61 CPDF_PageContentGenerator::~CPDF_PageContentGenerator() {}
     62 
     63 void 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 
    124 ByteString 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 
    152 bool 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 
    170 void 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.
    209 void 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
    265 void 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 
    323 void 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.
    353 void 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