|  | // 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 "fpdfsdk/fpdfxfa/cpdfxfa_page.h" | 
|  |  | 
|  | #include <memory> | 
|  | #include <utility> | 
|  |  | 
|  | #include "core/fpdfapi/page/cpdf_page.h" | 
|  | #include "core/fpdfapi/page/cpdf_pageimagecache.h" | 
|  | #include "core/fpdfapi/parser/cpdf_document.h" | 
|  | #include "fpdfsdk/cpdfsdk_pageview.h" | 
|  | #include "fpdfsdk/fpdfxfa/cpdfxfa_context.h" | 
|  | #include "fpdfsdk/fpdfxfa/cpdfxfa_widget.h" | 
|  | #include "third_party/base/check.h" | 
|  | #include "xfa/fgas/graphics/cfgas_gegraphics.h" | 
|  | #include "xfa/fxfa/cxfa_ffdocview.h" | 
|  | #include "xfa/fxfa/cxfa_ffpageview.h" | 
|  | #include "xfa/fxfa/cxfa_ffwidget.h" | 
|  | #include "xfa/fxfa/cxfa_ffwidgethandler.h" | 
|  |  | 
|  | namespace { | 
|  |  | 
|  | constexpr Mask<XFA_WidgetStatus> kIteratorFilter = { | 
|  | XFA_WidgetStatus::kVisible, | 
|  | XFA_WidgetStatus::kViewable, | 
|  | XFA_WidgetStatus::kFocused, | 
|  | }; | 
|  |  | 
|  | CXFA_FFWidget::IteratorIface* GCedWidgetIteratorForPage( | 
|  | CXFA_FFPageView* pFFPageView, | 
|  | CPDFSDK_PageView* pPageView) { | 
|  | if (!pFFPageView) | 
|  | return nullptr; | 
|  |  | 
|  | ObservedPtr<CPDFSDK_PageView> pWatchedPageView(pPageView); | 
|  | CXFA_FFWidget::IteratorIface* pIterator = | 
|  | pFFPageView->CreateGCedTraverseWidgetIterator(kIteratorFilter); | 
|  |  | 
|  | // Check |pPageView| again because JS may have destroyed it. | 
|  | return pWatchedPageView ? pIterator : nullptr; | 
|  | } | 
|  |  | 
|  | CXFA_FFWidget::IteratorIface* GCedWidgetIteratorForAnnot( | 
|  | CXFA_FFPageView* pFFPageView, | 
|  | CPDFSDK_Annot* pSDKAnnot) { | 
|  | if (!pFFPageView) | 
|  | return nullptr; | 
|  |  | 
|  | CPDFXFA_Widget* pXFAWidget = ToXFAWidget(pSDKAnnot); | 
|  | if (!pXFAWidget) | 
|  | return nullptr; | 
|  |  | 
|  | ObservedPtr<CPDFSDK_Annot> pObservedAnnot(pSDKAnnot); | 
|  | CXFA_FFWidget::IteratorIface* pWidgetIterator = | 
|  | pFFPageView->CreateGCedTraverseWidgetIterator(kIteratorFilter); | 
|  |  | 
|  | // Check |pSDKAnnot| again because JS may have destroyed it. | 
|  | if (!pObservedAnnot) | 
|  | return nullptr; | 
|  |  | 
|  | if (pWidgetIterator->GetCurrentWidget() != pXFAWidget->GetXFAFFWidget()) | 
|  | pWidgetIterator->SetCurrentWidget(pXFAWidget->GetXFAFFWidget()); | 
|  |  | 
|  | return pWidgetIterator; | 
|  | } | 
|  |  | 
|  | }  // namespace | 
|  |  | 
|  | CPDFXFA_Page::CPDFXFA_Page(CPDF_Document* pDocument, int page_index) | 
|  | : m_pDocument(pDocument), m_iPageIndex(page_index) { | 
|  | DCHECK(m_pDocument->GetExtension()); | 
|  | DCHECK(m_iPageIndex >= 0); | 
|  | } | 
|  |  | 
|  | CPDFXFA_Page::~CPDFXFA_Page() = default; | 
|  |  | 
|  | CPDF_Page* CPDFXFA_Page::AsPDFPage() { | 
|  | return m_pPDFPage.Get(); | 
|  | } | 
|  |  | 
|  | CPDFXFA_Page* CPDFXFA_Page::AsXFAPage() { | 
|  | return this; | 
|  | } | 
|  |  | 
|  | CPDF_Document* CPDFXFA_Page::GetDocument() const { | 
|  | return m_pDocument; | 
|  | } | 
|  |  | 
|  | bool CPDFXFA_Page::LoadPDFPage() { | 
|  | RetainPtr<CPDF_Dictionary> pDict = | 
|  | GetDocument()->GetMutablePageDictionary(m_iPageIndex); | 
|  | if (!pDict) | 
|  | return false; | 
|  |  | 
|  | if (!m_pPDFPage || m_pPDFPage->GetDict() != pDict) | 
|  | LoadPDFPageFromDict(std::move(pDict)); | 
|  |  | 
|  | return true; | 
|  | } | 
|  |  | 
|  | CXFA_FFPageView* CPDFXFA_Page::GetXFAPageView() const { | 
|  | auto* pContext = static_cast<CPDFXFA_Context*>(m_pDocument->GetExtension()); | 
|  | CXFA_FFDocView* pXFADocView = pContext->GetXFADocView(); | 
|  | return pXFADocView ? pXFADocView->GetPageView(m_iPageIndex) : nullptr; | 
|  | } | 
|  |  | 
|  | bool CPDFXFA_Page::LoadPage() { | 
|  | auto* pContext = static_cast<CPDFXFA_Context*>(m_pDocument->GetExtension()); | 
|  | switch (pContext->GetFormType()) { | 
|  | case FormType::kNone: | 
|  | case FormType::kAcroForm: | 
|  | case FormType::kXFAForeground: | 
|  | return LoadPDFPage(); | 
|  | case FormType::kXFAFull: | 
|  | return !!GetXFAPageView(); | 
|  | } | 
|  | } | 
|  |  | 
|  | void CPDFXFA_Page::LoadPDFPageFromDict(RetainPtr<CPDF_Dictionary> pPageDict) { | 
|  | DCHECK(pPageDict); | 
|  | m_pPDFPage = | 
|  | pdfium::MakeRetain<CPDF_Page>(GetDocument(), std::move(pPageDict)); | 
|  | m_pPDFPage->AddPageImageCache(); | 
|  | m_pPDFPage->ParseContent(); | 
|  | } | 
|  |  | 
|  | float CPDFXFA_Page::GetPageWidth() const { | 
|  | CXFA_FFPageView* pPageView = GetXFAPageView(); | 
|  | if (!m_pPDFPage && !pPageView) | 
|  | return 0.0f; | 
|  |  | 
|  | auto* pContext = static_cast<CPDFXFA_Context*>(m_pDocument->GetExtension()); | 
|  | switch (pContext->GetFormType()) { | 
|  | case FormType::kNone: | 
|  | case FormType::kAcroForm: | 
|  | case FormType::kXFAForeground: | 
|  | if (m_pPDFPage) | 
|  | return m_pPDFPage->GetPageWidth(); | 
|  | [[fallthrough]]; | 
|  | case FormType::kXFAFull: | 
|  | if (pPageView) | 
|  | return pPageView->GetPageViewRect().width; | 
|  | break; | 
|  | } | 
|  |  | 
|  | return 0.0f; | 
|  | } | 
|  |  | 
|  | float CPDFXFA_Page::GetPageHeight() const { | 
|  | CXFA_FFPageView* pPageView = GetXFAPageView(); | 
|  | if (!m_pPDFPage && !pPageView) | 
|  | return 0.0f; | 
|  |  | 
|  | auto* pContext = static_cast<CPDFXFA_Context*>(m_pDocument->GetExtension()); | 
|  | switch (pContext->GetFormType()) { | 
|  | case FormType::kNone: | 
|  | case FormType::kAcroForm: | 
|  | case FormType::kXFAForeground: | 
|  | if (m_pPDFPage) | 
|  | return m_pPDFPage->GetPageHeight(); | 
|  | [[fallthrough]]; | 
|  | case FormType::kXFAFull: | 
|  | if (pPageView) | 
|  | return pPageView->GetPageViewRect().height; | 
|  | break; | 
|  | } | 
|  |  | 
|  | return 0.0f; | 
|  | } | 
|  |  | 
|  | absl::optional<CFX_PointF> CPDFXFA_Page::DeviceToPage( | 
|  | const FX_RECT& rect, | 
|  | int rotate, | 
|  | const CFX_PointF& device_point) const { | 
|  | CXFA_FFPageView* pPageView = GetXFAPageView(); | 
|  | if (!m_pPDFPage && !pPageView) | 
|  | return absl::nullopt; | 
|  |  | 
|  | CFX_Matrix page2device = GetDisplayMatrix(rect, rotate); | 
|  | return page2device.GetInverse().Transform(device_point); | 
|  | } | 
|  |  | 
|  | absl::optional<CFX_PointF> CPDFXFA_Page::PageToDevice( | 
|  | const FX_RECT& rect, | 
|  | int rotate, | 
|  | const CFX_PointF& page_point) const { | 
|  | CXFA_FFPageView* pPageView = GetXFAPageView(); | 
|  | if (!m_pPDFPage && !pPageView) | 
|  | return absl::nullopt; | 
|  |  | 
|  | CFX_Matrix page2device = GetDisplayMatrix(rect, rotate); | 
|  | return page2device.Transform(page_point); | 
|  | } | 
|  |  | 
|  | CFX_Matrix CPDFXFA_Page::GetDisplayMatrix(const FX_RECT& rect, | 
|  | int iRotate) const { | 
|  | CXFA_FFPageView* pPageView = GetXFAPageView(); | 
|  | if (!m_pPDFPage && !pPageView) | 
|  | return CFX_Matrix(); | 
|  |  | 
|  | auto* pContext = static_cast<CPDFXFA_Context*>(m_pDocument->GetExtension()); | 
|  | switch (pContext->GetFormType()) { | 
|  | case FormType::kNone: | 
|  | case FormType::kAcroForm: | 
|  | case FormType::kXFAForeground: | 
|  | if (m_pPDFPage) | 
|  | return m_pPDFPage->GetDisplayMatrix(rect, iRotate); | 
|  | [[fallthrough]]; | 
|  | case FormType::kXFAFull: | 
|  | if (pPageView) | 
|  | return pPageView->GetDisplayMatrix(rect, iRotate); | 
|  | break; | 
|  | } | 
|  |  | 
|  | return CFX_Matrix(); | 
|  | } | 
|  |  | 
|  | CPDFSDK_Annot* CPDFXFA_Page::GetNextXFAAnnot(CPDFSDK_Annot* pSDKAnnot) const { | 
|  | CXFA_FFWidget::IteratorIface* pWidgetIterator = | 
|  | GCedWidgetIteratorForAnnot(GetXFAPageView(), pSDKAnnot); | 
|  | if (!pWidgetIterator) | 
|  | return nullptr; | 
|  |  | 
|  | return pSDKAnnot->GetPageView()->GetAnnotForFFWidget( | 
|  | pWidgetIterator->MoveToNext()); | 
|  | } | 
|  |  | 
|  | CPDFSDK_Annot* CPDFXFA_Page::GetPrevXFAAnnot(CPDFSDK_Annot* pSDKAnnot) const { | 
|  | CXFA_FFWidget::IteratorIface* pWidgetIterator = | 
|  | GCedWidgetIteratorForAnnot(GetXFAPageView(), pSDKAnnot); | 
|  | if (!pWidgetIterator) | 
|  | return nullptr; | 
|  |  | 
|  | return pSDKAnnot->GetPageView()->GetAnnotForFFWidget( | 
|  | pWidgetIterator->MoveToPrevious()); | 
|  | } | 
|  |  | 
|  | CPDFSDK_Annot* CPDFXFA_Page::GetFirstXFAAnnot( | 
|  | CPDFSDK_PageView* page_view) const { | 
|  | CXFA_FFWidget::IteratorIface* pWidgetIterator = | 
|  | GCedWidgetIteratorForPage(GetXFAPageView(), page_view); | 
|  | if (!pWidgetIterator) | 
|  | return nullptr; | 
|  |  | 
|  | return page_view->GetAnnotForFFWidget(pWidgetIterator->MoveToFirst()); | 
|  | } | 
|  |  | 
|  | CPDFSDK_Annot* CPDFXFA_Page::GetLastXFAAnnot( | 
|  | CPDFSDK_PageView* page_view) const { | 
|  | CXFA_FFWidget::IteratorIface* pWidgetIterator = | 
|  | GCedWidgetIteratorForPage(GetXFAPageView(), page_view); | 
|  | if (!pWidgetIterator) | 
|  | return nullptr; | 
|  |  | 
|  | return page_view->GetAnnotForFFWidget(pWidgetIterator->MoveToLast()); | 
|  | } | 
|  |  | 
|  | int CPDFXFA_Page::HasFormFieldAtPoint(const CFX_PointF& point) const { | 
|  | CXFA_FFPageView* pPageView = GetXFAPageView(); | 
|  | if (!pPageView) | 
|  | return -1; | 
|  |  | 
|  | CXFA_FFDocView* pDocView = pPageView->GetDocView(); | 
|  | if (!pDocView) | 
|  | return -1; | 
|  |  | 
|  | CXFA_FFWidgetHandler* pWidgetHandler = pDocView->GetWidgetHandler(); | 
|  | if (!pWidgetHandler) | 
|  | return -1; | 
|  | CXFA_FFPageWidgetIterator pWidgetIterator(pPageView, | 
|  | XFA_WidgetStatus::kViewable); | 
|  |  | 
|  | CXFA_FFWidget* pXFAAnnot; | 
|  | while ((pXFAAnnot = pWidgetIterator.MoveToNext()) != nullptr) { | 
|  | if (pXFAAnnot->GetFormFieldType() == FormFieldType::kXFA) | 
|  | continue; | 
|  |  | 
|  | CFX_FloatRect rcWidget = pXFAAnnot->GetWidgetRect().ToFloatRect(); | 
|  | rcWidget.Inflate(1.0f, 1.0f); | 
|  | if (rcWidget.Contains(point)) | 
|  | return static_cast<int>(pXFAAnnot->GetFormFieldType()); | 
|  | } | 
|  |  | 
|  | return -1; | 
|  | } | 
|  |  | 
|  | void CPDFXFA_Page::DrawFocusAnnot(CFX_RenderDevice* pDevice, | 
|  | CPDFSDK_Annot* pAnnot, | 
|  | const CFX_Matrix& mtUser2Device, | 
|  | const FX_RECT& rtClip) { | 
|  | CFX_RectF rectClip(rtClip); | 
|  | CFGAS_GEGraphics gs(pDevice); | 
|  | gs.SetClipRect(rectClip); | 
|  |  | 
|  | CXFA_FFPageView* xfaView = GetXFAPageView(); | 
|  | CXFA_FFPageWidgetIterator pWidgetIterator( | 
|  | xfaView, Mask<XFA_WidgetStatus>{XFA_WidgetStatus::kVisible, | 
|  | XFA_WidgetStatus::kViewable}); | 
|  |  | 
|  | while (true) { | 
|  | CXFA_FFWidget* pWidget = pWidgetIterator.MoveToNext(); | 
|  | if (!pWidget) | 
|  | break; | 
|  |  | 
|  | CFX_RectF rtWidgetBox = pWidget->GetBBox(CXFA_FFWidget::kDoNotDrawFocus); | 
|  | ++rtWidgetBox.width; | 
|  | ++rtWidgetBox.height; | 
|  | if (rtWidgetBox.IntersectWith(rectClip)) | 
|  | pWidget->RenderWidget(&gs, mtUser2Device, CXFA_FFWidget::kHighlight); | 
|  | } | 
|  |  | 
|  | CPDFXFA_Widget* pXFAWidget = ToXFAWidget(pAnnot); | 
|  | if (!pXFAWidget) | 
|  | return; | 
|  |  | 
|  | CXFA_FFDocView* docView = xfaView->GetDocView(); | 
|  | if (!docView) | 
|  | return; | 
|  |  | 
|  | docView->GetWidgetHandler()->RenderWidget(pXFAWidget->GetXFAFFWidget(), &gs, | 
|  | mtUser2Device, false); | 
|  | } |