422 lines
16 KiB
C++
422 lines
16 KiB
C++
// Copyright 2014 The PDFium Authors
|
|
// 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 "public/fpdf_transformpage.h"
|
|
|
|
#include <memory>
|
|
#include <sstream>
|
|
|
|
#include "constants/page_object.h"
|
|
#include "core/fpdfapi/edit/cpdf_contentstream_write_utils.h"
|
|
#include "core/fpdfapi/page/cpdf_clippath.h"
|
|
#include "core/fpdfapi/page/cpdf_page.h"
|
|
#include "core/fpdfapi/page/cpdf_pageobject.h"
|
|
#include "core/fpdfapi/page/cpdf_path.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_number.h"
|
|
#include "core/fpdfapi/parser/cpdf_reference.h"
|
|
#include "core/fpdfapi/parser/cpdf_stream.h"
|
|
#include "core/fxcrt/fx_string_wrappers.h"
|
|
#include "core/fxcrt/stl_util.h"
|
|
#include "core/fxge/cfx_fillrenderoptions.h"
|
|
#include "core/fxge/cfx_path.h"
|
|
#include "fpdfsdk/cpdfsdk_helpers.h"
|
|
#include "third_party/base/numerics/safe_conversions.h"
|
|
#include "third_party/base/span.h"
|
|
|
|
namespace {
|
|
|
|
void SetBoundingBox(CPDF_Page* page,
|
|
const ByteString& key,
|
|
const CFX_FloatRect& rect) {
|
|
if (!page)
|
|
return;
|
|
|
|
page->GetMutableDict()->SetRectFor(key, rect);
|
|
page->UpdateDimensions();
|
|
}
|
|
|
|
bool GetBoundingBox(const CPDF_Page* page,
|
|
const ByteString& key,
|
|
float* left,
|
|
float* bottom,
|
|
float* right,
|
|
float* top) {
|
|
if (!page || !left || !bottom || !right || !top)
|
|
return false;
|
|
|
|
RetainPtr<const CPDF_Array> pArray = page->GetDict()->GetArrayFor(key);
|
|
if (!pArray)
|
|
return false;
|
|
|
|
*left = pArray->GetFloatAt(0);
|
|
*bottom = pArray->GetFloatAt(1);
|
|
*right = pArray->GetFloatAt(2);
|
|
*top = pArray->GetFloatAt(3);
|
|
return true;
|
|
}
|
|
|
|
RetainPtr<CPDF_Object> GetPageContent(CPDF_Dictionary* pPageDict) {
|
|
return pPageDict->GetMutableDirectObjectFor(pdfium::page_object::kContents);
|
|
}
|
|
|
|
void OutputPath(fxcrt::ostringstream& buf, CPDF_Path path) {
|
|
const CFX_Path* pPath = path.GetObject();
|
|
if (!pPath)
|
|
return;
|
|
|
|
pdfium::span<const CFX_Path::Point> points = pPath->GetPoints();
|
|
if (path.IsRect()) {
|
|
CFX_PointF diff = points[2].m_Point - points[0].m_Point;
|
|
buf << points[0].m_Point.x << " " << points[0].m_Point.y << " " << diff.x
|
|
<< " " << diff.y << " re\n";
|
|
return;
|
|
}
|
|
|
|
for (size_t i = 0; i < points.size(); ++i) {
|
|
buf << points[i].m_Point.x << " " << points[i].m_Point.y;
|
|
CFX_Path::Point::Type point_type = points[i].m_Type;
|
|
if (point_type == CFX_Path::Point::Type::kMove) {
|
|
buf << " m\n";
|
|
} else if (point_type == CFX_Path::Point::Type::kBezier) {
|
|
buf << " " << points[i + 1].m_Point.x << " " << points[i + 1].m_Point.y
|
|
<< " " << points[i + 2].m_Point.x << " " << points[i + 2].m_Point.y;
|
|
buf << " c";
|
|
if (points[i + 2].m_CloseFigure)
|
|
buf << " h";
|
|
buf << "\n";
|
|
|
|
i += 2;
|
|
} else if (point_type == CFX_Path::Point::Type::kLine) {
|
|
buf << " l";
|
|
if (points[i].m_CloseFigure)
|
|
buf << " h";
|
|
buf << "\n";
|
|
}
|
|
}
|
|
}
|
|
|
|
} // namespace
|
|
|
|
FPDF_EXPORT void FPDF_CALLCONV FPDFPage_SetMediaBox(FPDF_PAGE page,
|
|
float left,
|
|
float bottom,
|
|
float right,
|
|
float top) {
|
|
SetBoundingBox(CPDFPageFromFPDFPage(page), pdfium::page_object::kMediaBox,
|
|
CFX_FloatRect(left, bottom, right, top));
|
|
}
|
|
|
|
FPDF_EXPORT void FPDF_CALLCONV FPDFPage_SetCropBox(FPDF_PAGE page,
|
|
float left,
|
|
float bottom,
|
|
float right,
|
|
float top) {
|
|
SetBoundingBox(CPDFPageFromFPDFPage(page), pdfium::page_object::kCropBox,
|
|
CFX_FloatRect(left, bottom, right, top));
|
|
}
|
|
|
|
FPDF_EXPORT void FPDF_CALLCONV FPDFPage_SetBleedBox(FPDF_PAGE page,
|
|
float left,
|
|
float bottom,
|
|
float right,
|
|
float top) {
|
|
SetBoundingBox(CPDFPageFromFPDFPage(page), pdfium::page_object::kBleedBox,
|
|
CFX_FloatRect(left, bottom, right, top));
|
|
}
|
|
|
|
FPDF_EXPORT void FPDF_CALLCONV FPDFPage_SetTrimBox(FPDF_PAGE page,
|
|
float left,
|
|
float bottom,
|
|
float right,
|
|
float top) {
|
|
SetBoundingBox(CPDFPageFromFPDFPage(page), pdfium::page_object::kTrimBox,
|
|
CFX_FloatRect(left, bottom, right, top));
|
|
}
|
|
|
|
FPDF_EXPORT void FPDF_CALLCONV FPDFPage_SetArtBox(FPDF_PAGE page,
|
|
float left,
|
|
float bottom,
|
|
float right,
|
|
float top) {
|
|
SetBoundingBox(CPDFPageFromFPDFPage(page), pdfium::page_object::kArtBox,
|
|
CFX_FloatRect(left, bottom, right, top));
|
|
}
|
|
|
|
FPDF_EXPORT FPDF_BOOL FPDF_CALLCONV FPDFPage_GetMediaBox(FPDF_PAGE page,
|
|
float* left,
|
|
float* bottom,
|
|
float* right,
|
|
float* top) {
|
|
return GetBoundingBox(CPDFPageFromFPDFPage(page),
|
|
pdfium::page_object::kMediaBox, left, bottom, right,
|
|
top);
|
|
}
|
|
|
|
FPDF_EXPORT FPDF_BOOL FPDF_CALLCONV FPDFPage_GetCropBox(FPDF_PAGE page,
|
|
float* left,
|
|
float* bottom,
|
|
float* right,
|
|
float* top) {
|
|
return GetBoundingBox(CPDFPageFromFPDFPage(page),
|
|
pdfium::page_object::kCropBox, left, bottom, right,
|
|
top);
|
|
}
|
|
|
|
FPDF_EXPORT FPDF_BOOL FPDF_CALLCONV FPDFPage_GetBleedBox(FPDF_PAGE page,
|
|
float* left,
|
|
float* bottom,
|
|
float* right,
|
|
float* top) {
|
|
return GetBoundingBox(CPDFPageFromFPDFPage(page),
|
|
pdfium::page_object::kBleedBox, left, bottom, right,
|
|
top);
|
|
}
|
|
|
|
FPDF_EXPORT FPDF_BOOL FPDF_CALLCONV FPDFPage_GetTrimBox(FPDF_PAGE page,
|
|
float* left,
|
|
float* bottom,
|
|
float* right,
|
|
float* top) {
|
|
return GetBoundingBox(CPDFPageFromFPDFPage(page),
|
|
pdfium::page_object::kTrimBox, left, bottom, right,
|
|
top);
|
|
}
|
|
|
|
FPDF_EXPORT FPDF_BOOL FPDF_CALLCONV FPDFPage_GetArtBox(FPDF_PAGE page,
|
|
float* left,
|
|
float* bottom,
|
|
float* right,
|
|
float* top) {
|
|
return GetBoundingBox(CPDFPageFromFPDFPage(page),
|
|
pdfium::page_object::kArtBox, left, bottom, right, top);
|
|
}
|
|
|
|
FPDF_EXPORT FPDF_BOOL FPDF_CALLCONV
|
|
FPDFPage_TransFormWithClip(FPDF_PAGE page,
|
|
const FS_MATRIX* matrix,
|
|
const FS_RECTF* clipRect) {
|
|
if (!matrix && !clipRect)
|
|
return false;
|
|
|
|
CPDF_Page* pPage = CPDFPageFromFPDFPage(page);
|
|
if (!pPage)
|
|
return false;
|
|
|
|
RetainPtr<CPDF_Dictionary> pPageDict = pPage->GetMutableDict();
|
|
RetainPtr<CPDF_Object> pContentObj = GetPageContent(pPageDict.Get());
|
|
if (!pContentObj)
|
|
return false;
|
|
|
|
CPDF_Document* pDoc = pPage->GetDocument();
|
|
if (!pDoc)
|
|
return false;
|
|
|
|
fxcrt::ostringstream text_buf;
|
|
text_buf << "q ";
|
|
|
|
if (clipRect) {
|
|
CFX_FloatRect rect = CFXFloatRectFromFSRectF(*clipRect);
|
|
rect.Normalize();
|
|
WriteRect(text_buf, rect) << " re W* n ";
|
|
}
|
|
if (matrix)
|
|
WriteMatrix(text_buf, CFXMatrixFromFSMatrix(*matrix)) << " cm ";
|
|
|
|
auto pStream = pDoc->NewIndirect<CPDF_Stream>(pDoc->New<CPDF_Dictionary>());
|
|
pStream->SetDataFromStringstream(&text_buf);
|
|
|
|
auto pEndStream =
|
|
pDoc->NewIndirect<CPDF_Stream>(pDoc->New<CPDF_Dictionary>());
|
|
pEndStream->SetData(ByteStringView(" Q").raw_span());
|
|
|
|
RetainPtr<CPDF_Array> pContentArray = ToArray(pContentObj);
|
|
if (pContentArray) {
|
|
pContentArray->InsertNewAt<CPDF_Reference>(0, pDoc, pStream->GetObjNum());
|
|
pContentArray->AppendNew<CPDF_Reference>(pDoc, pEndStream->GetObjNum());
|
|
} else if (pContentObj->IsStream() && !pContentObj->IsInline()) {
|
|
pContentArray = pDoc->NewIndirect<CPDF_Array>();
|
|
pContentArray->AppendNew<CPDF_Reference>(pDoc, pStream->GetObjNum());
|
|
pContentArray->AppendNew<CPDF_Reference>(pDoc, pContentObj->GetObjNum());
|
|
pContentArray->AppendNew<CPDF_Reference>(pDoc, pEndStream->GetObjNum());
|
|
pPageDict->SetNewFor<CPDF_Reference>(pdfium::page_object::kContents, pDoc,
|
|
pContentArray->GetObjNum());
|
|
}
|
|
|
|
// Need to transform the patterns as well.
|
|
RetainPtr<const CPDF_Dictionary> pRes =
|
|
pPageDict->GetDictFor(pdfium::page_object::kResources);
|
|
if (!pRes)
|
|
return true;
|
|
|
|
RetainPtr<const CPDF_Dictionary> pPatternDict = pRes->GetDictFor("Pattern");
|
|
if (!pPatternDict)
|
|
return true;
|
|
|
|
CPDF_DictionaryLocker locker(pPatternDict);
|
|
for (const auto& it : locker) {
|
|
RetainPtr<CPDF_Object> pObj = it.second;
|
|
if (pObj->IsReference())
|
|
pObj = pObj->GetMutableDirect();
|
|
|
|
RetainPtr<CPDF_Dictionary> pDict;
|
|
if (pObj->IsDictionary())
|
|
pDict.Reset(pObj->AsMutableDictionary());
|
|
else if (CPDF_Stream* pObjStream = pObj->AsMutableStream())
|
|
pDict = pObjStream->GetMutableDict();
|
|
else
|
|
continue;
|
|
|
|
if (matrix) {
|
|
CFX_Matrix m = CFXMatrixFromFSMatrix(*matrix);
|
|
pDict->SetMatrixFor("Matrix", pDict->GetMatrixFor("Matrix") * m);
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
FPDF_EXPORT void FPDF_CALLCONV
|
|
FPDFPageObj_TransformClipPath(FPDF_PAGEOBJECT page_object,
|
|
double a,
|
|
double b,
|
|
double c,
|
|
double d,
|
|
double e,
|
|
double f) {
|
|
CPDF_PageObject* pPageObj = CPDFPageObjectFromFPDFPageObject(page_object);
|
|
if (!pPageObj)
|
|
return;
|
|
|
|
CFX_Matrix matrix((float)a, (float)b, (float)c, (float)d, (float)e, (float)f);
|
|
|
|
// Special treatment to shading object, because the ClipPath for shading
|
|
// object is already transformed.
|
|
if (!pPageObj->IsShading())
|
|
pPageObj->TransformClipPath(matrix);
|
|
pPageObj->TransformGeneralState(matrix);
|
|
}
|
|
|
|
FPDF_EXPORT FPDF_CLIPPATH FPDF_CALLCONV
|
|
FPDFPageObj_GetClipPath(FPDF_PAGEOBJECT page_object) {
|
|
CPDF_PageObject* pPageObj = CPDFPageObjectFromFPDFPageObject(page_object);
|
|
if (!pPageObj)
|
|
return nullptr;
|
|
|
|
return FPDFClipPathFromCPDFClipPath(&pPageObj->m_ClipPath);
|
|
}
|
|
|
|
FPDF_EXPORT int FPDF_CALLCONV FPDFClipPath_CountPaths(FPDF_CLIPPATH clip_path) {
|
|
CPDF_ClipPath* pClipPath = CPDFClipPathFromFPDFClipPath(clip_path);
|
|
if (!pClipPath || !pClipPath->HasRef())
|
|
return -1;
|
|
|
|
return pdfium::base::checked_cast<int>(pClipPath->GetPathCount());
|
|
}
|
|
|
|
FPDF_EXPORT int FPDF_CALLCONV
|
|
FPDFClipPath_CountPathSegments(FPDF_CLIPPATH clip_path, int path_index) {
|
|
CPDF_ClipPath* pClipPath = CPDFClipPathFromFPDFClipPath(clip_path);
|
|
if (!pClipPath || !pClipPath->HasRef())
|
|
return -1;
|
|
|
|
if (path_index < 0 ||
|
|
static_cast<size_t>(path_index) >= pClipPath->GetPathCount()) {
|
|
return -1;
|
|
}
|
|
|
|
return fxcrt::CollectionSize<int>(pClipPath->GetPath(path_index).GetPoints());
|
|
}
|
|
|
|
FPDF_EXPORT FPDF_PATHSEGMENT FPDF_CALLCONV
|
|
FPDFClipPath_GetPathSegment(FPDF_CLIPPATH clip_path,
|
|
int path_index,
|
|
int segment_index) {
|
|
CPDF_ClipPath* pClipPath = CPDFClipPathFromFPDFClipPath(clip_path);
|
|
if (!pClipPath || !pClipPath->HasRef())
|
|
return nullptr;
|
|
|
|
if (path_index < 0 ||
|
|
static_cast<size_t>(path_index) >= pClipPath->GetPathCount()) {
|
|
return nullptr;
|
|
}
|
|
|
|
pdfium::span<const CFX_Path::Point> points =
|
|
pClipPath->GetPath(path_index).GetPoints();
|
|
if (!fxcrt::IndexInBounds(points, segment_index))
|
|
return nullptr;
|
|
|
|
return FPDFPathSegmentFromFXPathPoint(&points[segment_index]);
|
|
}
|
|
|
|
FPDF_EXPORT FPDF_CLIPPATH FPDF_CALLCONV FPDF_CreateClipPath(float left,
|
|
float bottom,
|
|
float right,
|
|
float top) {
|
|
CPDF_Path Path;
|
|
Path.AppendRect(left, bottom, right, top);
|
|
|
|
auto pNewClipPath = std::make_unique<CPDF_ClipPath>();
|
|
pNewClipPath->AppendPath(Path, CFX_FillRenderOptions::FillType::kEvenOdd);
|
|
|
|
// Caller takes ownership.
|
|
return FPDFClipPathFromCPDFClipPath(pNewClipPath.release());
|
|
}
|
|
|
|
FPDF_EXPORT void FPDF_CALLCONV FPDF_DestroyClipPath(FPDF_CLIPPATH clipPath) {
|
|
// Take ownership back from caller and destroy.
|
|
std::unique_ptr<CPDF_ClipPath>(CPDFClipPathFromFPDFClipPath(clipPath));
|
|
}
|
|
|
|
FPDF_EXPORT void FPDF_CALLCONV FPDFPage_InsertClipPath(FPDF_PAGE page,
|
|
FPDF_CLIPPATH clipPath) {
|
|
CPDF_Page* pPage = CPDFPageFromFPDFPage(page);
|
|
if (!pPage)
|
|
return;
|
|
|
|
RetainPtr<CPDF_Dictionary> pPageDict = pPage->GetMutableDict();
|
|
RetainPtr<CPDF_Object> pContentObj = GetPageContent(pPageDict.Get());
|
|
if (!pContentObj)
|
|
return;
|
|
|
|
fxcrt::ostringstream strClip;
|
|
CPDF_ClipPath* pClipPath = CPDFClipPathFromFPDFClipPath(clipPath);
|
|
for (size_t i = 0; i < pClipPath->GetPathCount(); ++i) {
|
|
CPDF_Path path = pClipPath->GetPath(i);
|
|
if (path.GetPoints().empty()) {
|
|
// Empty clipping (totally clipped out)
|
|
strClip << "0 0 m W n ";
|
|
} else {
|
|
OutputPath(strClip, path);
|
|
if (pClipPath->GetClipType(i) ==
|
|
CFX_FillRenderOptions::FillType::kWinding) {
|
|
strClip << "W n\n";
|
|
} else {
|
|
strClip << "W* n\n";
|
|
}
|
|
}
|
|
}
|
|
CPDF_Document* pDoc = pPage->GetDocument();
|
|
if (!pDoc)
|
|
return;
|
|
|
|
auto pStream = pDoc->NewIndirect<CPDF_Stream>(pDoc->New<CPDF_Dictionary>());
|
|
pStream->SetDataFromStringstream(&strClip);
|
|
|
|
RetainPtr<CPDF_Array> pArray = ToArray(pContentObj);
|
|
if (pArray) {
|
|
pArray->InsertNewAt<CPDF_Reference>(0, pDoc, pStream->GetObjNum());
|
|
} else if (pContentObj->IsStream() && !pContentObj->IsInline()) {
|
|
auto pContentArray = pDoc->NewIndirect<CPDF_Array>();
|
|
pContentArray->AppendNew<CPDF_Reference>(pDoc, pStream->GetObjNum());
|
|
pContentArray->AppendNew<CPDF_Reference>(pDoc, pContentObj->GetObjNum());
|
|
pPageDict->SetNewFor<CPDF_Reference>(pdfium::page_object::kContents, pDoc,
|
|
pContentArray->GetObjNum());
|
|
}
|
|
}
|