// Copyright 2016 PDFium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

// Original code copyright 2014 Foxit Software Inc. http://www.foxitsoftware.com

#include "core/fpdfapi/edit/cpdf_pagecontentgenerator.h"

#include <map>
#include <memory>
#include <set>
#include <tuple>
#include <utility>

#include "core/fpdfapi/edit/cpdf_contentstream_write_utils.h"
#include "core/fpdfapi/edit/cpdf_pagecontentmanager.h"
#include "core/fpdfapi/edit/cpdf_stringarchivestream.h"
#include "core/fpdfapi/font/cpdf_truetypefont.h"
#include "core/fpdfapi/font/cpdf_type1font.h"
#include "core/fpdfapi/page/cpdf_contentmarks.h"
#include "core/fpdfapi/page/cpdf_docpagedata.h"
#include "core/fpdfapi/page/cpdf_image.h"
#include "core/fpdfapi/page/cpdf_imageobject.h"
#include "core/fpdfapi/page/cpdf_page.h"
#include "core/fpdfapi/page/cpdf_path.h"
#include "core/fpdfapi/page/cpdf_pathobject.h"
#include "core/fpdfapi/page/cpdf_textobject.h"
#include "core/fpdfapi/parser/cpdf_array.h"
#include "core/fpdfapi/parser/cpdf_dictionary.h"
#include "core/fpdfapi/parser/cpdf_document.h"
#include "core/fpdfapi/parser/cpdf_name.h"
#include "core/fpdfapi/parser/cpdf_number.h"
#include "core/fpdfapi/parser/cpdf_reference.h"
#include "core/fpdfapi/parser/cpdf_stream.h"
#include "core/fpdfapi/parser/fpdf_parser_decode.h"
#include "core/fpdfapi/parser/fpdf_parser_utility.h"
#include "base/span.h"
#include "base/stl_util.h"

namespace {

bool GetColor(const CPDF_Color* pColor, float* rgb) {
  int intRGB[3];
  if (!pColor || !pColor->IsColorSpaceRGB() ||
      !pColor->GetRGB(&intRGB[0], &intRGB[1], &intRGB[2])) {
    return false;
  }
  rgb[0] = intRGB[0] / 255.0f;
  rgb[1] = intRGB[1] / 255.0f;
  rgb[2] = intRGB[2] / 255.0f;
  return true;
}

}  // namespace

CPDF_PageContentGenerator::CPDF_PageContentGenerator(
    CPDF_PageObjectHolder* pObjHolder)
    : m_pObjHolder(pObjHolder), m_pDocument(pObjHolder->GetDocument()) {
  for (const auto& pObj : *pObjHolder) {
    if (pObj)
      m_pageObjects.emplace_back(pObj.get());
  }
}

CPDF_PageContentGenerator::~CPDF_PageContentGenerator() = default;

void CPDF_PageContentGenerator::GenerateContent() {
  ASSERT(m_pObjHolder->IsPage());

  std::map<int32_t, std::unique_ptr<std::ostringstream>> stream =
      GenerateModifiedStreams();

  UpdateContentStreams(&stream);
}

std::map<int32_t, std::unique_ptr<std::ostringstream>>
CPDF_PageContentGenerator::GenerateModifiedStreams() {
  // Make sure default graphics are created.
  GetOrCreateDefaultGraphics();

  // Figure out which streams are dirty.
  std::set<int32_t> all_dirty_streams;
  for (auto& pPageObj : m_pageObjects) {
    if (pPageObj->IsDirty())
      all_dirty_streams.insert(pPageObj->GetContentStream());
  }
  std::set<int32_t> marked_dirty_streams = m_pObjHolder->TakeDirtyStreams();
  all_dirty_streams.insert(marked_dirty_streams.begin(),
                           marked_dirty_streams.end());

  // Start regenerating dirty streams.
  std::map<int32_t, std::unique_ptr<std::ostringstream>> streams;
  std::set<int32_t> empty_streams;
  std::unique_ptr<const CPDF_ContentMarks> empty_content_marks =
      std::make_unique<CPDF_ContentMarks>();
  std::map<int32_t, const CPDF_ContentMarks*> current_content_marks;

  for (int32_t dirty_stream : all_dirty_streams) {
    std::unique_ptr<std::ostringstream> buf =
        std::make_unique<std::ostringstream>();

    // Set the default graphic state values
    *buf << "q\n";
    if (!m_pObjHolder->GetLastCTM().IsIdentity())
      *buf << m_pObjHolder->GetLastCTM().GetInverse() << " cm\n";

    ProcessDefaultGraphics(buf.get());

    streams[dirty_stream] = std::move(buf);
    empty_streams.insert(dirty_stream);
    current_content_marks[dirty_stream] = empty_content_marks.get();
  }

  // Process the page objects, write into each dirty stream.
  for (auto& pPageObj : m_pageObjects) {
    int stream_index = pPageObj->GetContentStream();
    auto it = streams.find(stream_index);
    if (it == streams.end())
      continue;

    std::ostringstream* buf = it->second.get();
    empty_streams.erase(stream_index);
    current_content_marks[stream_index] = ProcessContentMarks(
        buf, pPageObj.Get(), current_content_marks[stream_index]);
    ProcessPageObject(buf, pPageObj.Get());
  }

  // Finish dirty streams.
  for (int32_t dirty_stream : all_dirty_streams) {
    std::ostringstream* buf = streams[dirty_stream].get();
    if (pdfium::Contains(empty_streams, dirty_stream)) {
      // Clear to show that this stream needs to be deleted.
      buf->str("");
    } else {
      FinishMarks(buf, current_content_marks[dirty_stream]);

      // Return graphics to original state
      *buf << "Q\n";
    }
  }

  return streams;
}

void CPDF_PageContentGenerator::UpdateContentStreams(
    std::map<int32_t, std::unique_ptr<std::ostringstream>>* new_stream_data) {
  // If no streams were regenerated or removed, nothing to do here.
  if (new_stream_data->empty())
    return;

  CPDF_PageContentManager page_content_manager(m_pObjHolder.Get());

  for (auto& pair : *new_stream_data) {
    int32_t stream_index = pair.first;
    std::ostringstream* buf = pair.second.get();

    if (stream_index == CPDF_PageObject::kNoContentStream) {
      int new_stream_index = page_content_manager.AddStream(buf);
      UpdateStreamlessPageObjects(new_stream_index);
      continue;
    }

    CPDF_Stream* old_stream =
        page_content_manager.GetStreamByIndex(stream_index);
    ASSERT(old_stream);

    // If buf is now empty, remove the stream instead of setting the data.
    if (buf->tellp() <= 0)
      page_content_manager.ScheduleRemoveStreamByIndex(stream_index);
    else
      old_stream->SetDataFromStringstreamAndRemoveFilter(buf);
  }

  page_content_manager.ExecuteScheduledRemovals();
}

ByteString CPDF_PageContentGenerator::RealizeResource(
    const CPDF_Object* pResource,
    const ByteString& bsType) const {
  ASSERT(pResource);
  if (!m_pObjHolder->m_pResources) {
    m_pObjHolder->m_pResources.Reset(
        m_pDocument->NewIndirect<CPDF_Dictionary>());
    m_pObjHolder->GetDict()->SetNewFor<CPDF_Reference>(
        "Resources", m_pDocument.Get(),
        m_pObjHolder->m_pResources->GetObjNum());
  }
  CPDF_Dictionary* pResList = m_pObjHolder->m_pResources->GetDictFor(bsType);
  if (!pResList)
    pResList = m_pObjHolder->m_pResources->SetNewFor<CPDF_Dictionary>(bsType);

  ByteString name;
  int idnum = 1;
  while (1) {
    name = ByteString::Format("FX%c%d", bsType[0], idnum);
    if (!pResList->KeyExist(name))
      break;

    idnum++;
  }
  pResList->SetNewFor<CPDF_Reference>(name, m_pDocument.Get(),
                                      pResource->GetObjNum());
  return name;
}

bool CPDF_PageContentGenerator::ProcessPageObjects(std::ostringstream* buf) {
  bool bDirty = false;
  std::unique_ptr<const CPDF_ContentMarks> empty_content_marks =
      std::make_unique<CPDF_ContentMarks>();
  const CPDF_ContentMarks* content_marks = empty_content_marks.get();

  for (auto& pPageObj : m_pageObjects) {
    if (m_pObjHolder->IsPage() && !pPageObj->IsDirty())
      continue;

    bDirty = true;
    content_marks = ProcessContentMarks(buf, pPageObj.Get(), content_marks);
    ProcessPageObject(buf, pPageObj.Get());
  }
  FinishMarks(buf, content_marks);
  return bDirty;
}

void CPDF_PageContentGenerator::UpdateStreamlessPageObjects(
    int new_content_stream_index) {
  for (auto& pPageObj : m_pageObjects) {
    if (pPageObj->GetContentStream() == CPDF_PageObject::kNoContentStream)
      pPageObj->SetContentStream(new_content_stream_index);
  }
}

const CPDF_ContentMarks* CPDF_PageContentGenerator::ProcessContentMarks(
    std::ostringstream* buf,
    const CPDF_PageObject* pPageObj,
    const CPDF_ContentMarks* pPrev) {
  const CPDF_ContentMarks* pNext = &pPageObj->m_ContentMarks;

  size_t first_different = pPrev->FindFirstDifference(pNext);

  // Close all marks that are in prev but not in next.
  // Technically we should iterate backwards to close from the top to the
  // bottom, but since the EMC operators do not identify which mark they are
  // closing, it does not matter.
  for (size_t i = first_different; i < pPrev->CountItems(); ++i)
    *buf << "EMC\n";

  // Open all marks that are in next but not in prev.
  for (size_t i = first_different; i < pNext->CountItems(); ++i) {
    const CPDF_ContentMarkItem* item = pNext->GetItem(i);

    // Write mark tag.
    *buf << "/" << PDF_NameEncode(item->GetName()) << " ";

    // If there are no parameters, write a BMC (begin marked content) operator.
    if (item->GetParamType() == CPDF_ContentMarkItem::kNone) {
      *buf << "BMC\n";
      continue;
    }

    // If there are parameters, write properties, direct or indirect.
    switch (item->GetParamType()) {
      case CPDF_ContentMarkItem::kDirectDict: {
        CPDF_StringArchiveStream archive_stream(buf);
        item->GetParam()->WriteTo(&archive_stream, nullptr);
        *buf << " ";
        break;
      }
      case CPDF_ContentMarkItem::kPropertiesDict: {
        *buf << "/" << item->GetPropertyName() << " ";
        break;
      }
      default:
        NOTREACHED();
        break;
    }

    // Write BDC (begin dictionary content) operator.
    *buf << "BDC\n";
  }

  return pNext;
}

void CPDF_PageContentGenerator::FinishMarks(
    std::ostringstream* buf,
    const CPDF_ContentMarks* pContentMarks) {
  // Technically we should iterate backwards to close from the top to the
  // bottom, but since the EMC operators do not identify which mark they are
  // closing, it does not matter.
  for (size_t i = 0; i < pContentMarks->CountItems(); ++i)
    *buf << "EMC\n";
}

void CPDF_PageContentGenerator::ProcessPageObject(std::ostringstream* buf,
                                                  CPDF_PageObject* pPageObj) {
  if (CPDF_ImageObject* pImageObject = pPageObj->AsImage())
    ProcessImage(buf, pImageObject);
  else if (CPDF_PathObject* pPathObj = pPageObj->AsPath())
    ProcessPath(buf, pPathObj);
  else if (CPDF_TextObject* pTextObj = pPageObj->AsText())
    ProcessText(buf, pTextObj);
  pPageObj->SetDirty(false);
}

void CPDF_PageContentGenerator::ProcessImage(std::ostringstream* buf,
                                             CPDF_ImageObject* pImageObj) {
  if ((pImageObj->matrix().a == 0 && pImageObj->matrix().b == 0) ||
      (pImageObj->matrix().c == 0 && pImageObj->matrix().d == 0)) {
    return;
  }
  *buf << "q " << pImageObj->matrix() << " cm ";

  RetainPtr<CPDF_Image> pImage = pImageObj->GetImage();
  if (pImage->IsInline())
    return;

  CPDF_Stream* pStream = pImage->GetStream();
  if (!pStream)
    return;

  bool bWasInline = pStream->IsInline();
  if (bWasInline)
    pImage->ConvertStreamToIndirectObject();

  ByteString name = RealizeResource(pStream, "XObject");
  if (bWasInline) {
    auto* pPageData = CPDF_DocPageData::FromDocument(m_pDocument.Get());
    pImageObj->SetImage(pPageData->GetImage(pStream->GetObjNum()));
  }

  *buf << "/" << PDF_NameEncode(name) << " Do Q\n";
}

// Processing path construction with operators from Table 4.9 of PDF spec 1.7:
// "re" appends a rectangle (here, used only if the whole path is a rectangle)
// "m" moves current point to the given coordinates
// "l" creates a line from current point to the new point
// "c" adds a Bezier curve from current to last point, using the two other
// points as the Bezier control points
// Note: "l", "c" change the current point
// "h" closes the subpath (appends a line from current to starting point)
void CPDF_PageContentGenerator::ProcessPathPoints(std::ostringstream* buf,
                                                  CPDF_Path* pPath) {
  pdfium::span<const FX_PATHPOINT> points = pPath->GetPoints();
  if (pPath->IsRect()) {
    CFX_PointF diff = points[2].m_Point - points[0].m_Point;
    *buf << points[0].m_Point << " " << diff << " re";
    return;
  }
  for (size_t i = 0; i < points.size(); ++i) {
    if (i > 0)
      *buf << " ";

    *buf << points[i].m_Point;

    FXPT_TYPE point_type = points[i].m_Type;
    if (point_type == FXPT_TYPE::MoveTo) {
      *buf << " m";
    } else if (point_type == FXPT_TYPE::LineTo) {
      *buf << " l";
    } else if (point_type == FXPT_TYPE::BezierTo) {
      if (i + 2 >= points.size() ||
          !points[i].IsTypeAndOpen(FXPT_TYPE::BezierTo) ||
          !points[i + 1].IsTypeAndOpen(FXPT_TYPE::BezierTo) ||
          points[i + 2].m_Type != FXPT_TYPE::BezierTo) {
        // If format is not supported, close the path and paint
        *buf << " h";
        break;
      }
      *buf << " ";
      *buf << points[i + 1].m_Point << " ";
      *buf << points[i + 2].m_Point << " c";
      i += 2;
    }
    if (points[i].m_CloseFigure)
      *buf << " h";
  }
}

// Processing path painting with operators from Table 4.10 of PDF spec 1.7:
// Path painting operators: "S", "n", "B", "f", "B*", "f*", depending on
// the filling mode and whether we want stroking the path or not.
// "Q" restores the graphics state imposed by the ProcessGraphics method.
void CPDF_PageContentGenerator::ProcessPath(std::ostringstream* buf,
                                            CPDF_PathObject* pPathObj) {
  ProcessGraphics(buf, pPathObj);

  *buf << pPathObj->matrix() << " cm ";
  ProcessPathPoints(buf, &pPathObj->path());

  if (pPathObj->has_no_filltype())
    *buf << (pPathObj->stroke() ? " S" : " n");
  else if (pPathObj->has_winding_filltype())
    *buf << (pPathObj->stroke() ? " B" : " f");
  else if (pPathObj->has_alternate_filltype())
    *buf << (pPathObj->stroke() ? " B*" : " f*");
  *buf << " Q\n";
}

// This method supports color operators rg and RGB from Table 4.24 of PDF spec
// 1.7. A color will not be set if the colorspace is not DefaultRGB or the RGB
// values cannot be obtained. The method also adds an external graphics
// dictionary, as described in Section 4.3.4.
// "rg" sets the fill color, "RG" sets the stroke color (using DefaultRGB)
// "w" sets the stroke line width.
// "ca" sets the fill alpha, "CA" sets the stroke alpha.
// "W" and "W*" modify the clipping path using the nonzero winding rule and
// even-odd rules, respectively.
// "q" saves the graphics state, so that the settings can later be reversed
void CPDF_PageContentGenerator::ProcessGraphics(std::ostringstream* buf,
                                                CPDF_PageObject* pPageObj) {
  *buf << "q ";
  float fillColor[3];
  if (GetColor(pPageObj->m_ColorState.GetFillColor(), fillColor)) {
    *buf << fillColor[0] << " " << fillColor[1] << " " << fillColor[2]
         << " rg ";
  }
  float strokeColor[3];
  if (GetColor(pPageObj->m_ColorState.GetStrokeColor(), strokeColor)) {
    *buf << strokeColor[0] << " " << strokeColor[1] << " " << strokeColor[2]
         << " RG ";
  }
  float lineWidth = pPageObj->m_GraphState.GetLineWidth();
  if (lineWidth != 1.0f)
    WriteFloat(*buf, lineWidth) << " w ";
  CFX_GraphStateData::LineCap lineCap = pPageObj->m_GraphState.GetLineCap();
  if (lineCap != CFX_GraphStateData::LineCapButt)
    *buf << static_cast<int>(lineCap) << " J ";
  CFX_GraphStateData::LineJoin lineJoin = pPageObj->m_GraphState.GetLineJoin();
  if (lineJoin != CFX_GraphStateData::LineJoinMiter)
    *buf << static_cast<int>(lineJoin) << " j ";

  const CPDF_ClipPath& clip_path = pPageObj->m_ClipPath;
  if (clip_path.HasRef()) {
    for (size_t i = 0; i < clip_path.GetPathCount(); ++i) {
      CPDF_Path path = clip_path.GetPath(i);
      ProcessPathPoints(buf, &path);
      switch (clip_path.GetClipType(i)) {
        case CFX_FillRenderOptions::FillType::kWinding:
          *buf << " W ";
          break;
        case CFX_FillRenderOptions::FillType::kEvenOdd:
          *buf << " W* ";
          break;
        case CFX_FillRenderOptions::FillType::kNoFill:
          NOTREACHED();
          break;
      }

      // Use a no-op path-painting operator to terminate the path without
      // causing any marks to be placed on the page.
      *buf << "n 0 g ";
    }
  }

  GraphicsData graphD;
  graphD.fillAlpha = pPageObj->m_GeneralState.GetFillAlpha();
  graphD.strokeAlpha = pPageObj->m_GeneralState.GetStrokeAlpha();
  graphD.blendType = pPageObj->m_GeneralState.GetBlendType();
  if (graphD.fillAlpha == 1.0f && graphD.strokeAlpha == 1.0f &&
      graphD.blendType == BlendMode::kNormal) {
    return;
  }

  ByteString name;
  auto it = m_pObjHolder->m_GraphicsMap.find(graphD);
  if (it != m_pObjHolder->m_GraphicsMap.end()) {
    name = it->second;
  } else {
    auto gsDict = pdfium::MakeRetain<CPDF_Dictionary>();
    if (graphD.fillAlpha != 1.0f)
      gsDict->SetNewFor<CPDF_Number>("ca", graphD.fillAlpha);

    if (graphD.strokeAlpha != 1.0f)
      gsDict->SetNewFor<CPDF_Number>("CA", graphD.strokeAlpha);

    if (graphD.blendType != BlendMode::kNormal) {
      gsDict->SetNewFor<CPDF_Name>("BM",
                                   pPageObj->m_GeneralState.GetBlendMode());
    }
    CPDF_Object* pDict = m_pDocument->AddIndirectObject(gsDict);
    name = RealizeResource(pDict, "ExtGState");
    m_pObjHolder->m_GraphicsMap[graphD] = name;
  }
  *buf << "/" << PDF_NameEncode(name) << " gs ";
}

void CPDF_PageContentGenerator::ProcessDefaultGraphics(
    std::ostringstream* buf) {
  *buf << "0 0 0 RG 0 0 0 rg 1 w "
       << static_cast<int>(CFX_GraphStateData::LineCapButt) << " J "
       << static_cast<int>(CFX_GraphStateData::LineJoinMiter) << " j\n";
  ByteString name = GetOrCreateDefaultGraphics();
  *buf << "/" << PDF_NameEncode(name) << " gs ";
}

ByteString CPDF_PageContentGenerator::GetOrCreateDefaultGraphics() const {
  GraphicsData defaultGraphics;
  defaultGraphics.fillAlpha = 1.0f;
  defaultGraphics.strokeAlpha = 1.0f;
  defaultGraphics.blendType = BlendMode::kNormal;
  auto it = m_pObjHolder->m_GraphicsMap.find(defaultGraphics);

  // If default graphics already exists, return it.
  if (it != m_pObjHolder->m_GraphicsMap.end())
    return it->second;

  // Otherwise, create them.
  auto gsDict = pdfium::MakeRetain<CPDF_Dictionary>();
  gsDict->SetNewFor<CPDF_Number>("ca", defaultGraphics.fillAlpha);
  gsDict->SetNewFor<CPDF_Number>("CA", defaultGraphics.strokeAlpha);
  gsDict->SetNewFor<CPDF_Name>("BM", "Normal");
  CPDF_Object* pDict = m_pDocument->AddIndirectObject(gsDict);
  ByteString name = RealizeResource(pDict, "ExtGState");
  m_pObjHolder->m_GraphicsMap[defaultGraphics] = name;
  return name;
}

// This method adds text to the buffer, BT begins the text object, ET ends it.
// Tm sets the text matrix (allows positioning and transforming text).
// Tf sets the font name (from Font in Resources) and font size.
// Tr sets the text rendering mode.
// Tj sets the actual text, <####...> is used when specifying charcodes.
void CPDF_PageContentGenerator::ProcessText(std::ostringstream* buf,
                                            CPDF_TextObject* pTextObj) {
  ProcessGraphics(buf, pTextObj);
  *buf << "BT " << pTextObj->GetTextMatrix() << " Tm ";
  RetainPtr<CPDF_Font> pFont(pTextObj->GetFont());
  if (!pFont)
    pFont = CPDF_Font::GetStockFont(m_pDocument.Get(), "Helvetica");

  FontData data;
  const CPDF_FontEncoding* pEncoding = nullptr;
  if (pFont->IsType1Font()) {
    data.type = "Type1";
    pEncoding = pFont->AsType1Font()->GetEncoding();
  } else if (pFont->IsTrueTypeFont()) {
    data.type = "TrueType";
    pEncoding = pFont->AsTrueTypeFont()->GetEncoding();
  } else if (pFont->IsCIDFont()) {
    data.type = "Type0";
  } else {
    return;
  }
  data.baseFont = pFont->GetBaseFontName();
  auto it = m_pObjHolder->m_FontsMap.find(data);
  ByteString dictName;
  if (it != m_pObjHolder->m_FontsMap.end()) {
    dictName = it->second;
  } else {
    CPDF_Object* pIndirectFont = pFont->GetFontDict();
    if (pIndirectFont->IsInline()) {
      // In this case we assume it must be a standard font
      auto pFontDict = pdfium::MakeRetain<CPDF_Dictionary>();
      pFontDict->SetNewFor<CPDF_Name>("Type", "Font");
      pFontDict->SetNewFor<CPDF_Name>("Subtype", data.type);
      pFontDict->SetNewFor<CPDF_Name>("BaseFont", data.baseFont);
      if (pEncoding) {
        pFontDict->SetFor("Encoding",
                          pEncoding->Realize(m_pDocument->GetByteStringPool()));
      }
      pIndirectFont = m_pDocument->AddIndirectObject(pFontDict);
    }
    dictName = RealizeResource(pIndirectFont, "Font");
    m_pObjHolder->m_FontsMap[data] = dictName;
  }
  *buf << "/" << PDF_NameEncode(dictName) << " ";
  WriteFloat(*buf, pTextObj->GetFontSize()) << " Tf ";
  *buf << static_cast<int>(pTextObj->GetTextRenderMode()) << " Tr ";
  ByteString text;
  for (uint32_t charcode : pTextObj->GetCharCodes()) {
    if (charcode != CPDF_Font::kInvalidCharCode)
      pFont->AppendChar(&text, charcode);
  }
  *buf << PDF_EncodeString(text, true) << " Tj ET";
  *buf << " Q\n";
}
