| // 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/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<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->mutable_clip_path()); |
| } |
| |
| 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()); |
| } |
| } |