// 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 #include #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/containers/span.h" #include "third_party/base/numerics/safe_conversions.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 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 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 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 pPageDict = pPage->GetMutableDict(); RetainPtr 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(pDoc->New()); pStream->SetDataFromStringstream(&text_buf); auto pEndStream = pDoc->NewIndirect(pDoc->New()); pEndStream->SetData(ByteStringView(" Q").raw_span()); RetainPtr pContentArray = ToArray(pContentObj); if (pContentArray) { pContentArray->InsertNewAt(0, pDoc, pStream->GetObjNum()); pContentArray->AppendNew(pDoc, pEndStream->GetObjNum()); } else if (pContentObj->IsStream() && !pContentObj->IsInline()) { pContentArray = pDoc->NewIndirect(); pContentArray->AppendNew(pDoc, pStream->GetObjNum()); pContentArray->AppendNew(pDoc, pContentObj->GetObjNum()); pContentArray->AppendNew(pDoc, pEndStream->GetObjNum()); pPageDict->SetNewFor(pdfium::page_object::kContents, pDoc, pContentArray->GetObjNum()); } // Need to transform the patterns as well. RetainPtr pRes = pPageDict->GetDictFor(pdfium::page_object::kResources); if (!pRes) return true; RetainPtr pPatternDict = pRes->GetDictFor("Pattern"); if (!pPatternDict) return true; CPDF_DictionaryLocker locker(pPatternDict); for (const auto& it : locker) { RetainPtr pObj = it.second; if (pObj->IsReference()) pObj = pObj->GetMutableDirect(); RetainPtr 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(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(path_index) >= pClipPath->GetPathCount()) { return -1; } return fxcrt::CollectionSize(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(path_index) >= pClipPath->GetPathCount()) { return nullptr; } pdfium::span 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(); 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(CPDFClipPathFromFPDFClipPath(clipPath)); } FPDF_EXPORT void FPDF_CALLCONV FPDFPage_InsertClipPath(FPDF_PAGE page, FPDF_CLIPPATH clipPath) { CPDF_Page* pPage = CPDFPageFromFPDFPage(page); if (!pPage) return; RetainPtr pPageDict = pPage->GetMutableDict(); RetainPtr 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(pDoc->New()); pStream->SetDataFromStringstream(&strClip); RetainPtr pArray = ToArray(pContentObj); if (pArray) { pArray->InsertNewAt(0, pDoc, pStream->GetObjNum()); } else if (pContentObj->IsStream() && !pContentObj->IsInline()) { auto pContentArray = pDoc->NewIndirect(); pContentArray->AppendNew(pDoc, pStream->GetObjNum()); pContentArray->AppendNew(pDoc, pContentObj->GetObjNum()); pPageDict->SetNewFor(pdfium::page_object::kContents, pDoc, pContentArray->GetObjNum()); } }