| // Copyright 2016 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 "core/fpdfapi/page/cpdf_page.h" | |
| #include <set> | |
| #include <utility> | |
| #include "constants/page_object.h" | |
| #include "core/fpdfapi/page/cpdf_contentparser.h" | |
| #include "core/fpdfapi/page/cpdf_pageimagecache.h" | |
| #include "core/fpdfapi/page/cpdf_pageobject.h" | |
| #include "core/fpdfapi/parser/cpdf_array.h" | |
| #include "core/fpdfapi/parser/cpdf_dictionary.h" | |
| #include "core/fpdfapi/parser/cpdf_name.h" | |
| #include "core/fpdfapi/parser/cpdf_object.h" | |
| #include "core/fxcrt/check.h" | |
| #include "core/fxcrt/check_op.h" | |
| #include "core/fxcrt/containers/contains.h" | |
| CPDF_Page::CPDF_Page(CPDF_Document* document, | |
| RetainPtr<CPDF_Dictionary> pPageDict) | |
| : CPDF_PageObjectHolder(document, std::move(pPageDict), nullptr, nullptr), | |
| page_size_(100, 100), | |
| pdf_document_(document) { | |
| // Retrieves `pPageDict` and validates it. | |
| RetainPtr<CPDF_Dictionary> mutable_page_dict = GetMutableDict(); | |
| CHECK(IsValidPageDictLoose(mutable_page_dict)); | |
| // Then fix it up if /Type is missing. | |
| if (!mutable_page_dict->KeyExist(pdfium::page_object::kType)) { | |
| mutable_page_dict->SetNewFor<CPDF_Name>(pdfium::page_object::kType, "Page"); | |
| CHECK(IsValidPageDict(mutable_page_dict)); | |
| } | |
| // Cannot initialize |resources_| and |page_resources_| via the | |
| // CPDF_PageObjectHolder ctor because GetPageAttr() requires | |
| // CPDF_PageObjectHolder to finish initializing first. | |
| RetainPtr<CPDF_Object> pPageAttr = | |
| GetMutablePageAttr(pdfium::page_object::kResources); | |
| resources_ = pPageAttr ? pPageAttr->GetMutableDict() : nullptr; | |
| page_resources_ = resources_; | |
| UpdateDimensions(); | |
| transparency_.SetIsolated(); | |
| LoadTransparencyInfo(); | |
| } | |
| CPDF_Page::~CPDF_Page() = default; | |
| // static | |
| bool CPDF_Page::IsValidPageDict(const CPDF_Dictionary* page_dict) { | |
| return IsValidPageDictLoose(page_dict) && | |
| page_dict->KeyExist(pdfium::page_object::kType); | |
| } | |
| // static | |
| bool CPDF_Page::IsValidPageDictLoose(const CPDF_Dictionary* page_dict) { | |
| if (!page_dict) { | |
| return false; | |
| } | |
| RetainPtr<const CPDF_Object> type = | |
| page_dict->GetObjectFor(pdfium::page_object::kType); | |
| if (!type) { | |
| return true; | |
| } | |
| RetainPtr<const CPDF_Name> name_type = ToName(type->GetDirect()); | |
| return name_type && name_type->GetString() == "Page"; | |
| } | |
| CPDF_Page* CPDF_Page::AsPDFPage() { | |
| return this; | |
| } | |
| CPDFXFA_Page* CPDF_Page::AsXFAPage() { | |
| return nullptr; | |
| } | |
| CPDF_Document* CPDF_Page::GetDocument() const { | |
| return pdf_document_; | |
| } | |
| float CPDF_Page::GetPageWidth() const { | |
| return page_size_.width; | |
| } | |
| float CPDF_Page::GetPageHeight() const { | |
| return page_size_.height; | |
| } | |
| bool CPDF_Page::IsPage() const { | |
| return true; | |
| } | |
| void CPDF_Page::ParseContent() { | |
| if (GetParseState() == ParseState::kParsed) { | |
| return; | |
| } | |
| if (GetParseState() == ParseState::kNotParsed) { | |
| StartParse(std::make_unique<CPDF_ContentParser>(this)); | |
| } | |
| DCHECK_EQ(GetParseState(), ParseState::kParsing); | |
| ContinueParse(nullptr); | |
| } | |
| RetainPtr<CPDF_Object> CPDF_Page::GetMutablePageAttr(ByteStringView name) { | |
| return pdfium::WrapRetain(const_cast<CPDF_Object*>(GetPageAttr(name).Get())); | |
| } | |
| RetainPtr<const CPDF_Object> CPDF_Page::GetPageAttr(ByteStringView name) const { | |
| std::set<RetainPtr<const CPDF_Dictionary>> visited; | |
| RetainPtr<const CPDF_Dictionary> pPageDict = GetDict(); | |
| while (pPageDict && !pdfium::Contains(visited, pPageDict)) { | |
| RetainPtr<const CPDF_Object> pObj = pPageDict->GetDirectObjectFor(name); | |
| if (pObj) { | |
| return pObj; | |
| } | |
| visited.insert(pPageDict); | |
| pPageDict = pPageDict->GetDictFor(pdfium::page_object::kParent); | |
| } | |
| return nullptr; | |
| } | |
| CFX_FloatRect CPDF_Page::GetBox(ByteStringView name) const { | |
| CFX_FloatRect box; | |
| RetainPtr<const CPDF_Array> pBox = ToArray(GetPageAttr(name)); | |
| if (pBox) { | |
| box = pBox->GetRect(); | |
| box.Normalize(); | |
| } | |
| return box; | |
| } | |
| std::optional<CFX_PointF> CPDF_Page::DeviceToPage( | |
| const FX_RECT& rect, | |
| int rotation, | |
| const CFX_PointF& device_point) const { | |
| CFX_Matrix page2device = GetDisplayMatrixForRect(rect, rotation); | |
| return page2device.GetInverse().Transform(device_point); | |
| } | |
| std::optional<CFX_PointF> CPDF_Page::PageToDevice( | |
| const FX_RECT& rect, | |
| int rotation, | |
| const CFX_PointF& page_point) const { | |
| CFX_Matrix page2device = GetDisplayMatrixForRect(rect, rotation); | |
| return page2device.Transform(page_point); | |
| } | |
| CFX_Matrix CPDF_Page::GetDisplayMatrixForRect(const FX_RECT& rect, | |
| int rotation) const { | |
| return GetDisplayMatrixForFloatRect(CFX_FloatRect(rect), rotation); | |
| } | |
| CFX_Matrix CPDF_Page::GetDisplayMatrixForFloatRect(const CFX_FloatRect& rect, | |
| int rotation) const { | |
| if (page_size_.width == 0 || page_size_.height == 0) { | |
| return CFX_Matrix(); | |
| } | |
| float x0; | |
| float y0; | |
| float x1; | |
| float y1; | |
| float x2; | |
| float y2; | |
| // This code implicitly inverts the y-axis to account for page coordinates | |
| // pointing up and bitmap coordinates pointing down. (x0, y0) is the base | |
| // point, (x1, y1) is that point translated on y and (x2, y2) is the point | |
| // translated on x. On rotation = 0, y0 is rect.top and the translation to get | |
| // y1 is performed as negative. This results in the desired transformation. | |
| switch (rotation % 4) { | |
| case 0: | |
| x0 = rect.left; | |
| y0 = rect.top; | |
| x1 = rect.left; | |
| y1 = rect.bottom; | |
| x2 = rect.right; | |
| y2 = rect.top; | |
| break; | |
| case 1: | |
| x0 = rect.left; | |
| y0 = rect.bottom; | |
| x1 = rect.right; | |
| y1 = rect.bottom; | |
| x2 = rect.left; | |
| y2 = rect.top; | |
| break; | |
| case 2: | |
| x0 = rect.right; | |
| y0 = rect.bottom; | |
| x1 = rect.right; | |
| y1 = rect.top; | |
| x2 = rect.left; | |
| y2 = rect.bottom; | |
| break; | |
| case 3: | |
| x0 = rect.right; | |
| y0 = rect.top; | |
| x1 = rect.left; | |
| y1 = rect.top; | |
| x2 = rect.right; | |
| y2 = rect.bottom; | |
| break; | |
| default: | |
| CHECK_LT(rotation, 0); | |
| // Handing this with `rotation += 4` breaks public API compatibility. So | |
| // just return early here without doing all the matrix calculations below. | |
| return CFX_Matrix(0, 0, 0, 0, 0, 0); | |
| } | |
| CFX_Matrix matrix((x2 - x0) / page_size_.width, (y2 - y0) / page_size_.width, | |
| (x1 - x0) / page_size_.height, | |
| (y1 - y0) / page_size_.height, x0, y0); | |
| return page_matrix_ * matrix; | |
| } | |
| CFX_Matrix CPDF_Page::GetDisplayMatrix() const { | |
| const CFX_FloatRect rect(0, 0, GetPageWidth(), GetPageHeight()); | |
| return GetDisplayMatrixForFloatRect(rect, 0); | |
| } | |
| int CPDF_Page::GetPageRotation() const { | |
| RetainPtr<const CPDF_Object> pRotate = | |
| GetPageAttr(pdfium::page_object::kRotate); | |
| int rotation = pRotate ? (pRotate->GetInteger() / 90) % 4 : 0; | |
| return (rotation < 0) ? (rotation + 4) : rotation; | |
| } | |
| RetainPtr<CPDF_Array> CPDF_Page::GetOrCreateAnnotsArray() { | |
| return GetMutableDict()->GetOrCreateArrayFor("Annots"); | |
| } | |
| RetainPtr<CPDF_Array> CPDF_Page::GetMutableAnnotsArray() { | |
| return GetMutableDict()->GetMutableArrayFor("Annots"); | |
| } | |
| RetainPtr<const CPDF_Array> CPDF_Page::GetAnnotsArray() const { | |
| return GetDict()->GetArrayFor("Annots"); | |
| } | |
| void CPDF_Page::AddPageImageCache() { | |
| page_image_cache_ = std::make_unique<CPDF_PageImageCache>(this); | |
| } | |
| void CPDF_Page::SetRenderContext(std::unique_ptr<RenderContextIface> context) { | |
| DCHECK(!render_context_); | |
| DCHECK(context); | |
| render_context_ = std::move(context); | |
| } | |
| void CPDF_Page::ClearRenderContext() { | |
| render_context_.reset(); | |
| } | |
| void CPDF_Page::ClearView() { | |
| if (view_) { | |
| view_->ClearPage(this); | |
| } | |
| } | |
| void CPDF_Page::UpdateDimensions() { | |
| CFX_FloatRect mediabox = GetBox(pdfium::page_object::kMediaBox); | |
| if (mediabox.IsEmpty()) { | |
| mediabox = CFX_FloatRect(0, 0, 612, 792); | |
| } | |
| bbox_ = GetBox(pdfium::page_object::kCropBox); | |
| if (bbox_.IsEmpty()) { | |
| bbox_ = mediabox; | |
| } else { | |
| bbox_.Intersect(mediabox); | |
| } | |
| page_size_.width = bbox_.Width(); | |
| page_size_.height = bbox_.Height(); | |
| switch (GetPageRotation()) { | |
| case 0: | |
| page_matrix_ = CFX_Matrix(1.0f, 0, 0, 1.0f, -bbox_.left, -bbox_.bottom); | |
| break; | |
| case 1: | |
| std::swap(page_size_.width, page_size_.height); | |
| page_matrix_ = CFX_Matrix(0, -1.0f, 1.0f, 0, -bbox_.bottom, bbox_.right); | |
| break; | |
| case 2: | |
| page_matrix_ = CFX_Matrix(-1.0f, 0, 0, -1.0f, bbox_.right, bbox_.top); | |
| break; | |
| case 3: | |
| std::swap(page_size_.width, page_size_.height); | |
| page_matrix_ = CFX_Matrix(0, 1.0f, -1.0f, 0, bbox_.top, -bbox_.left); | |
| break; | |
| } | |
| } | |
| CPDF_Page::RenderContextClearer::RenderContextClearer(CPDF_Page* pPage) | |
| : page_(pPage) {} | |
| CPDF_Page::RenderContextClearer::~RenderContextClearer() { | |
| if (page_) { | |
| page_->ClearRenderContext(); | |
| } | |
| } |