From 971a674ef17526ad37ce55ba90110830b94889d0 Mon Sep 17 00:00:00 2001 From: Dan Sinclair Date: Wed, 28 Mar 2018 19:23:25 +0000 Subject: Rename fpdfsdk/fpdf* files to be consistent This CL makes the fpdfsdk/fpdf* files to be consistently prefixed with fpdf_ instead of randomly dropping the _. Change-Id: I23e3c8a0831b56bcd17c788d9fe874b2ab8b24fc Reviewed-on: https://pdfium-review.googlesource.com/29390 Commit-Queue: dsinclair Reviewed-by: Henrique Nakashima --- fpdfsdk/fpdf_annot.cpp | 887 ++++++++++++++++++ fpdfsdk/fpdf_annot_embeddertest.cpp | 1458 +++++++++++++++++++++++++++++ fpdfsdk/fpdf_attachment.cpp | 275 ++++++ fpdfsdk/fpdf_attachment_embeddertest.cpp | 260 ++++++ fpdfsdk/fpdf_catalog.cpp | 22 + fpdfsdk/fpdf_catalog_unittest.cpp | 97 ++ fpdfsdk/fpdf_doc.cpp | 429 +++++++++ fpdfsdk/fpdf_doc_embeddertest.cpp | 424 +++++++++ fpdfsdk/fpdf_doc_unittest.cpp | 271 ++++++ fpdfsdk/fpdf_edit_embeddertest.cpp | 1469 ++++++++++++++++++++++++++++++ fpdfsdk/fpdf_editimg.cpp | 310 +++++++ fpdfsdk/fpdf_editimg_unittest.cpp | 71 ++ fpdfsdk/fpdf_editpage.cpp | 427 +++++++++ fpdfsdk/fpdf_editpath.cpp | 292 ++++++ fpdfsdk/fpdf_editpath_embeddertest.cpp | 63 ++ fpdfsdk/fpdf_edittext.cpp | 499 ++++++++++ fpdfsdk/fpdf_ext_embeddertest.cpp | 24 + fpdfsdk/fpdf_formfill.cpp | 836 +++++++++++++++++ fpdfsdk/fpdf_formfill_embeddertest.cpp | 1376 ++++++++++++++++++++++++++++ fpdfsdk/fpdf_ppo_embeddertest.cpp | 278 ++++++ fpdfsdk/fpdf_save.cpp | 287 ++++++ fpdfsdk/fpdf_save_embeddertest.cpp | 101 ++ fpdfsdk/fpdf_text.cpp | 393 ++++++++ fpdfsdk/fpdf_text_embeddertest.cpp | 756 +++++++++++++++ fpdfsdk/fpdf_view.cpp | 1159 +++++++++++++++++++++++ fpdfsdk/fpdf_view_c_api_test.c | 382 ++++++++ fpdfsdk/fpdf_view_c_api_test.h | 20 + fpdfsdk/fpdf_view_embeddertest.cpp | 605 ++++++++++++ fpdfsdk/fpdfannot.cpp | 887 ------------------ fpdfsdk/fpdfannot_embeddertest.cpp | 1457 ----------------------------- fpdfsdk/fpdfattachment.cpp | 275 ------ fpdfsdk/fpdfattachment_embeddertest.cpp | 260 ------ fpdfsdk/fpdfcatalog.cpp | 22 - fpdfsdk/fpdfcatalog_unittest.cpp | 95 -- fpdfsdk/fpdfdoc.cpp | 429 --------- fpdfsdk/fpdfdoc_embeddertest.cpp | 424 --------- fpdfsdk/fpdfdoc_unittest.cpp | 271 ------ fpdfsdk/fpdfedit_embeddertest.cpp | 1469 ------------------------------ fpdfsdk/fpdfeditimg.cpp | 310 ------- fpdfsdk/fpdfeditimg_unittest.cpp | 71 -- fpdfsdk/fpdfeditpage.cpp | 427 --------- fpdfsdk/fpdfeditpath.cpp | 292 ------ fpdfsdk/fpdfeditpath_embeddertest.cpp | 63 -- fpdfsdk/fpdfedittext.cpp | 499 ---------- fpdfsdk/fpdfext_embeddertest.cpp | 24 - fpdfsdk/fpdfformfill.cpp | 836 ----------------- fpdfsdk/fpdfformfill_embeddertest.cpp | 1377 ---------------------------- fpdfsdk/fpdfppo_embeddertest.cpp | 278 ------ fpdfsdk/fpdfsave.cpp | 287 ------ fpdfsdk/fpdfsave_embeddertest.cpp | 101 -- fpdfsdk/fpdftext.cpp | 393 -------- fpdfsdk/fpdftext_embeddertest.cpp | 756 --------------- fpdfsdk/fpdfview.cpp | 1158 ----------------------- fpdfsdk/fpdfview_c_api_test.c | 382 -------- fpdfsdk/fpdfview_c_api_test.h | 20 - fpdfsdk/fpdfview_embeddertest.cpp | 605 ------------ 56 files changed, 13471 insertions(+), 13468 deletions(-) create mode 100644 fpdfsdk/fpdf_annot.cpp create mode 100644 fpdfsdk/fpdf_annot_embeddertest.cpp create mode 100644 fpdfsdk/fpdf_attachment.cpp create mode 100644 fpdfsdk/fpdf_attachment_embeddertest.cpp create mode 100644 fpdfsdk/fpdf_catalog.cpp create mode 100644 fpdfsdk/fpdf_catalog_unittest.cpp create mode 100644 fpdfsdk/fpdf_doc.cpp create mode 100644 fpdfsdk/fpdf_doc_embeddertest.cpp create mode 100644 fpdfsdk/fpdf_doc_unittest.cpp create mode 100644 fpdfsdk/fpdf_edit_embeddertest.cpp create mode 100644 fpdfsdk/fpdf_editimg.cpp create mode 100644 fpdfsdk/fpdf_editimg_unittest.cpp create mode 100644 fpdfsdk/fpdf_editpage.cpp create mode 100644 fpdfsdk/fpdf_editpath.cpp create mode 100644 fpdfsdk/fpdf_editpath_embeddertest.cpp create mode 100644 fpdfsdk/fpdf_edittext.cpp create mode 100644 fpdfsdk/fpdf_ext_embeddertest.cpp create mode 100644 fpdfsdk/fpdf_formfill.cpp create mode 100644 fpdfsdk/fpdf_formfill_embeddertest.cpp create mode 100644 fpdfsdk/fpdf_ppo_embeddertest.cpp create mode 100644 fpdfsdk/fpdf_save.cpp create mode 100644 fpdfsdk/fpdf_save_embeddertest.cpp create mode 100644 fpdfsdk/fpdf_text.cpp create mode 100644 fpdfsdk/fpdf_text_embeddertest.cpp create mode 100644 fpdfsdk/fpdf_view.cpp create mode 100644 fpdfsdk/fpdf_view_c_api_test.c create mode 100644 fpdfsdk/fpdf_view_c_api_test.h create mode 100644 fpdfsdk/fpdf_view_embeddertest.cpp delete mode 100644 fpdfsdk/fpdfannot.cpp delete mode 100644 fpdfsdk/fpdfannot_embeddertest.cpp delete mode 100644 fpdfsdk/fpdfattachment.cpp delete mode 100644 fpdfsdk/fpdfattachment_embeddertest.cpp delete mode 100644 fpdfsdk/fpdfcatalog.cpp delete mode 100644 fpdfsdk/fpdfcatalog_unittest.cpp delete mode 100644 fpdfsdk/fpdfdoc.cpp delete mode 100644 fpdfsdk/fpdfdoc_embeddertest.cpp delete mode 100644 fpdfsdk/fpdfdoc_unittest.cpp delete mode 100644 fpdfsdk/fpdfedit_embeddertest.cpp delete mode 100644 fpdfsdk/fpdfeditimg.cpp delete mode 100644 fpdfsdk/fpdfeditimg_unittest.cpp delete mode 100644 fpdfsdk/fpdfeditpage.cpp delete mode 100644 fpdfsdk/fpdfeditpath.cpp delete mode 100644 fpdfsdk/fpdfeditpath_embeddertest.cpp delete mode 100644 fpdfsdk/fpdfedittext.cpp delete mode 100644 fpdfsdk/fpdfext_embeddertest.cpp delete mode 100644 fpdfsdk/fpdfformfill.cpp delete mode 100644 fpdfsdk/fpdfformfill_embeddertest.cpp delete mode 100644 fpdfsdk/fpdfppo_embeddertest.cpp delete mode 100644 fpdfsdk/fpdfsave.cpp delete mode 100644 fpdfsdk/fpdfsave_embeddertest.cpp delete mode 100644 fpdfsdk/fpdftext.cpp delete mode 100644 fpdfsdk/fpdftext_embeddertest.cpp delete mode 100644 fpdfsdk/fpdfview.cpp delete mode 100644 fpdfsdk/fpdfview_c_api_test.c delete mode 100644 fpdfsdk/fpdfview_c_api_test.h delete mode 100644 fpdfsdk/fpdfview_embeddertest.cpp (limited to 'fpdfsdk') diff --git a/fpdfsdk/fpdf_annot.cpp b/fpdfsdk/fpdf_annot.cpp new file mode 100644 index 0000000000..2ab0bca454 --- /dev/null +++ b/fpdfsdk/fpdf_annot.cpp @@ -0,0 +1,887 @@ +// Copyright 2017 PDFium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "public/fpdf_annot.h" + +#include +#include + +#include "core/fpdfapi/edit/cpdf_pagecontentgenerator.h" +#include "core/fpdfapi/page/cpdf_form.h" +#include "core/fpdfapi/page/cpdf_page.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_document.h" +#include "core/fpdfapi/parser/cpdf_name.h" +#include "core/fpdfapi/parser/cpdf_number.h" +#include "core/fpdfapi/parser/cpdf_string.h" +#include "core/fpdfdoc/cpdf_annot.h" +#include "core/fpdfdoc/cpdf_formfield.h" +#include "core/fpdfdoc/cpdf_interform.h" +#include "core/fpdfdoc/cpvt_generateap.h" +#include "core/fxge/cfx_color.h" +#include "fpdfsdk/cpdfsdk_helpers.h" + +namespace { + +// These checks ensure the consistency of annotation subtype values across core/ +// and public. +static_assert(static_cast(CPDF_Annot::Subtype::UNKNOWN) == + FPDF_ANNOT_UNKNOWN, + "CPDF_Annot::UNKNOWN value mismatch"); +static_assert(static_cast(CPDF_Annot::Subtype::TEXT) == FPDF_ANNOT_TEXT, + "CPDF_Annot::TEXT value mismatch"); +static_assert(static_cast(CPDF_Annot::Subtype::LINK) == FPDF_ANNOT_LINK, + "CPDF_Annot::LINK value mismatch"); +static_assert(static_cast(CPDF_Annot::Subtype::FREETEXT) == + FPDF_ANNOT_FREETEXT, + "CPDF_Annot::FREETEXT value mismatch"); +static_assert(static_cast(CPDF_Annot::Subtype::LINE) == FPDF_ANNOT_LINE, + "CPDF_Annot::LINE value mismatch"); +static_assert(static_cast(CPDF_Annot::Subtype::SQUARE) == + FPDF_ANNOT_SQUARE, + "CPDF_Annot::SQUARE value mismatch"); +static_assert(static_cast(CPDF_Annot::Subtype::CIRCLE) == + FPDF_ANNOT_CIRCLE, + "CPDF_Annot::CIRCLE value mismatch"); +static_assert(static_cast(CPDF_Annot::Subtype::POLYGON) == + FPDF_ANNOT_POLYGON, + "CPDF_Annot::POLYGON value mismatch"); +static_assert(static_cast(CPDF_Annot::Subtype::POLYLINE) == + FPDF_ANNOT_POLYLINE, + "CPDF_Annot::POLYLINE value mismatch"); +static_assert(static_cast(CPDF_Annot::Subtype::HIGHLIGHT) == + FPDF_ANNOT_HIGHLIGHT, + "CPDF_Annot::HIGHLIGHT value mismatch"); +static_assert(static_cast(CPDF_Annot::Subtype::UNDERLINE) == + FPDF_ANNOT_UNDERLINE, + "CPDF_Annot::UNDERLINE value mismatch"); +static_assert(static_cast(CPDF_Annot::Subtype::SQUIGGLY) == + FPDF_ANNOT_SQUIGGLY, + "CPDF_Annot::SQUIGGLY value mismatch"); +static_assert(static_cast(CPDF_Annot::Subtype::STRIKEOUT) == + FPDF_ANNOT_STRIKEOUT, + "CPDF_Annot::STRIKEOUT value mismatch"); +static_assert(static_cast(CPDF_Annot::Subtype::STAMP) == FPDF_ANNOT_STAMP, + "CPDF_Annot::STAMP value mismatch"); +static_assert(static_cast(CPDF_Annot::Subtype::CARET) == FPDF_ANNOT_CARET, + "CPDF_Annot::CARET value mismatch"); +static_assert(static_cast(CPDF_Annot::Subtype::INK) == FPDF_ANNOT_INK, + "CPDF_Annot::INK value mismatch"); +static_assert(static_cast(CPDF_Annot::Subtype::POPUP) == FPDF_ANNOT_POPUP, + "CPDF_Annot::POPUP value mismatch"); +static_assert(static_cast(CPDF_Annot::Subtype::FILEATTACHMENT) == + FPDF_ANNOT_FILEATTACHMENT, + "CPDF_Annot::FILEATTACHMENT value mismatch"); +static_assert(static_cast(CPDF_Annot::Subtype::SOUND) == FPDF_ANNOT_SOUND, + "CPDF_Annot::SOUND value mismatch"); +static_assert(static_cast(CPDF_Annot::Subtype::MOVIE) == FPDF_ANNOT_MOVIE, + "CPDF_Annot::MOVIE value mismatch"); +static_assert(static_cast(CPDF_Annot::Subtype::WIDGET) == + FPDF_ANNOT_WIDGET, + "CPDF_Annot::WIDGET value mismatch"); +static_assert(static_cast(CPDF_Annot::Subtype::SCREEN) == + FPDF_ANNOT_SCREEN, + "CPDF_Annot::SCREEN value mismatch"); +static_assert(static_cast(CPDF_Annot::Subtype::PRINTERMARK) == + FPDF_ANNOT_PRINTERMARK, + "CPDF_Annot::PRINTERMARK value mismatch"); +static_assert(static_cast(CPDF_Annot::Subtype::TRAPNET) == + FPDF_ANNOT_TRAPNET, + "CPDF_Annot::TRAPNET value mismatch"); +static_assert(static_cast(CPDF_Annot::Subtype::WATERMARK) == + FPDF_ANNOT_WATERMARK, + "CPDF_Annot::WATERMARK value mismatch"); +static_assert(static_cast(CPDF_Annot::Subtype::THREED) == + FPDF_ANNOT_THREED, + "CPDF_Annot::THREED value mismatch"); +static_assert(static_cast(CPDF_Annot::Subtype::RICHMEDIA) == + FPDF_ANNOT_RICHMEDIA, + "CPDF_Annot::RICHMEDIA value mismatch"); +static_assert(static_cast(CPDF_Annot::Subtype::XFAWIDGET) == + FPDF_ANNOT_XFAWIDGET, + "CPDF_Annot::XFAWIDGET value mismatch"); + +// These checks ensure the consistency of annotation appearance mode values +// across core/ and public. +static_assert(static_cast(CPDF_Annot::AppearanceMode::Normal) == + FPDF_ANNOT_APPEARANCEMODE_NORMAL, + "CPDF_Annot::AppearanceMode::Normal value mismatch"); +static_assert(static_cast(CPDF_Annot::AppearanceMode::Rollover) == + FPDF_ANNOT_APPEARANCEMODE_ROLLOVER, + "CPDF_Annot::AppearanceMode::Rollover value mismatch"); +static_assert(static_cast(CPDF_Annot::AppearanceMode::Down) == + FPDF_ANNOT_APPEARANCEMODE_DOWN, + "CPDF_Annot::AppearanceMode::Down value mismatch"); + +// These checks ensure the consistency of dictionary value types across core/ +// and public/. +static_assert(static_cast(CPDF_Object::Type::BOOLEAN) == + FPDF_OBJECT_BOOLEAN, + "CPDF_Object::BOOLEAN value mismatch"); +static_assert(static_cast(CPDF_Object::Type::NUMBER) == FPDF_OBJECT_NUMBER, + "CPDF_Object::NUMBER value mismatch"); +static_assert(static_cast(CPDF_Object::Type::STRING) == FPDF_OBJECT_STRING, + "CPDF_Object::STRING value mismatch"); +static_assert(static_cast(CPDF_Object::Type::NAME) == FPDF_OBJECT_NAME, + "CPDF_Object::NAME value mismatch"); +static_assert(static_cast(CPDF_Object::Type::ARRAY) == FPDF_OBJECT_ARRAY, + "CPDF_Object::ARRAY value mismatch"); +static_assert(static_cast(CPDF_Object::Type::DICTIONARY) == + FPDF_OBJECT_DICTIONARY, + "CPDF_Object::DICTIONARY value mismatch"); +static_assert(static_cast(CPDF_Object::Type::STREAM) == FPDF_OBJECT_STREAM, + "CPDF_Object::STREAM value mismatch"); +static_assert(static_cast(CPDF_Object::Type::NULLOBJ) == + FPDF_OBJECT_NULLOBJ, + "CPDF_Object::NULLOBJ value mismatch"); +static_assert(static_cast(CPDF_Object::Type::REFERENCE) == + FPDF_OBJECT_REFERENCE, + "CPDF_Object::REFERENCE value mismatch"); + +class CPDF_AnnotContext { + public: + CPDF_AnnotContext(CPDF_Dictionary* pAnnotDict, + CPDF_Page* pPage, + CPDF_Stream* pStream) + : m_pAnnotDict(pAnnotDict), m_pPage(pPage) { + SetForm(pStream); + } + ~CPDF_AnnotContext() {} + + bool HasForm() const { return !!m_pAnnotForm; } + + void SetForm(CPDF_Stream* pStream) { + if (!pStream) + return; + + // Reset the annotation matrix to be the identity matrix, since the + // appearance stream already takes matrix into account. + pStream->GetDict()->SetMatrixFor("Matrix", CFX_Matrix()); + + m_pAnnotForm = pdfium::MakeUnique( + m_pPage->m_pDocument.Get(), m_pPage->m_pResources.Get(), pStream); + m_pAnnotForm->ParseContent(); + } + + CPDF_Form* GetForm() const { return m_pAnnotForm.get(); } + CPDF_Dictionary* GetAnnotDict() const { return m_pAnnotDict.Get(); } + CPDF_Page* GetPage() const { return m_pPage.Get(); } + + private: + std::unique_ptr m_pAnnotForm; + UnownedPtr m_pAnnotDict; + UnownedPtr m_pPage; +}; + +CPDF_AnnotContext* CPDFAnnotContextFromFPDFAnnotation(FPDF_ANNOTATION annot) { + return static_cast(annot); +} + +bool HasAPStream(const CPDF_Dictionary* pAnnotDict) { + return !!FPDFDOC_GetAnnotAP(pAnnotDict, CPDF_Annot::AppearanceMode::Normal); +} + +void UpdateContentStream(CPDF_Form* pForm, CPDF_Stream* pStream) { + ASSERT(pForm); + ASSERT(pStream); + + CPDF_PageContentGenerator generator(pForm); + std::ostringstream buf; + generator.ProcessPageObjects(&buf); + pStream->SetDataAndRemoveFilter(&buf); +} + +} // namespace + +FPDF_EXPORT FPDF_BOOL FPDF_CALLCONV +FPDFAnnot_IsSupportedSubtype(FPDF_ANNOTATION_SUBTYPE subtype) { + // The supported subtypes must also be communicated in the user doc. + return subtype == FPDF_ANNOT_CIRCLE || subtype == FPDF_ANNOT_FREETEXT || + subtype == FPDF_ANNOT_HIGHLIGHT || subtype == FPDF_ANNOT_INK || + subtype == FPDF_ANNOT_POPUP || subtype == FPDF_ANNOT_SQUARE || + subtype == FPDF_ANNOT_SQUIGGLY || subtype == FPDF_ANNOT_STAMP || + subtype == FPDF_ANNOT_STRIKEOUT || subtype == FPDF_ANNOT_TEXT || + subtype == FPDF_ANNOT_UNDERLINE; +} + +FPDF_EXPORT FPDF_ANNOTATION FPDF_CALLCONV +FPDFPage_CreateAnnot(FPDF_PAGE page, FPDF_ANNOTATION_SUBTYPE subtype) { + CPDF_Page* pPage = CPDFPageFromFPDFPage(page); + if (!pPage || !FPDFAnnot_IsSupportedSubtype(subtype)) + return nullptr; + + auto pDict = pdfium::MakeUnique( + pPage->m_pDocument->GetByteStringPool()); + pDict->SetNewFor("Type", "Annot"); + pDict->SetNewFor("Subtype", + CPDF_Annot::AnnotSubtypeToString( + static_cast(subtype))); + auto pNewAnnot = + pdfium::MakeUnique(pDict.get(), pPage, nullptr); + + CPDF_Array* pAnnotList = pPage->m_pFormDict->GetArrayFor("Annots"); + if (!pAnnotList) + pAnnotList = pPage->m_pFormDict->SetNewFor("Annots"); + + pAnnotList->Add(std::move(pDict)); + return pNewAnnot.release(); +} + +FPDF_EXPORT int FPDF_CALLCONV FPDFPage_GetAnnotCount(FPDF_PAGE page) { + CPDF_Page* pPage = CPDFPageFromFPDFPage(page); + if (!pPage || !pPage->m_pFormDict) + return 0; + + CPDF_Array* pAnnots = pPage->m_pFormDict->GetArrayFor("Annots"); + return pAnnots ? pAnnots->GetCount() : 0; +} + +FPDF_EXPORT FPDF_ANNOTATION FPDF_CALLCONV FPDFPage_GetAnnot(FPDF_PAGE page, + int index) { + CPDF_Page* pPage = CPDFPageFromFPDFPage(page); + if (!pPage || !pPage->m_pFormDict || index < 0) + return nullptr; + + CPDF_Array* pAnnots = pPage->m_pFormDict->GetArrayFor("Annots"); + if (!pAnnots || static_cast(index) >= pAnnots->GetCount()) + return nullptr; + + CPDF_Dictionary* pDict = ToDictionary(pAnnots->GetDirectObjectAt(index)); + auto pNewAnnot = pdfium::MakeUnique(pDict, pPage, nullptr); + return pNewAnnot.release(); +} + +FPDF_EXPORT int FPDF_CALLCONV FPDFPage_GetAnnotIndex(FPDF_PAGE page, + FPDF_ANNOTATION annot) { + CPDF_Page* pPage = CPDFPageFromFPDFPage(page); + CPDF_AnnotContext* pAnnot = CPDFAnnotContextFromFPDFAnnotation(annot); + if (!pPage || !pPage->m_pFormDict || !pAnnot || !pAnnot->GetAnnotDict()) + return -1; + + CPDF_Array* pAnnots = pPage->m_pFormDict->GetArrayFor("Annots"); + if (!pAnnots) + return -1; + + CPDF_Dictionary* pDict = pAnnot->GetAnnotDict(); + auto it = + std::find_if(pAnnots->begin(), pAnnots->end(), + [pDict](const std::unique_ptr& candidate) { + return candidate->GetDirect() == pDict; + }); + + if (it == pAnnots->end()) + return -1; + + return it - pAnnots->begin(); +} + +FPDF_EXPORT void FPDF_CALLCONV FPDFPage_CloseAnnot(FPDF_ANNOTATION annot) { + delete CPDFAnnotContextFromFPDFAnnotation(annot); +} + +FPDF_EXPORT FPDF_BOOL FPDF_CALLCONV FPDFPage_RemoveAnnot(FPDF_PAGE page, + int index) { + CPDF_Page* pPage = CPDFPageFromFPDFPage(page); + if (!pPage || !pPage->m_pFormDict || index < 0) + return false; + + CPDF_Array* pAnnots = pPage->m_pFormDict->GetArrayFor("Annots"); + if (!pAnnots || static_cast(index) >= pAnnots->GetCount()) + return false; + + pAnnots->RemoveAt(index); + return true; +} + +FPDF_EXPORT FPDF_ANNOTATION_SUBTYPE FPDF_CALLCONV +FPDFAnnot_GetSubtype(FPDF_ANNOTATION annot) { + if (!annot) + return FPDF_ANNOT_UNKNOWN; + + CPDF_Dictionary* pAnnotDict = + CPDFAnnotContextFromFPDFAnnotation(annot)->GetAnnotDict(); + if (!pAnnotDict) + return FPDF_ANNOT_UNKNOWN; + + return static_cast( + CPDF_Annot::StringToAnnotSubtype(pAnnotDict->GetStringFor("Subtype"))); +} + +FPDF_EXPORT FPDF_BOOL FPDF_CALLCONV +FPDFAnnot_IsObjectSupportedSubtype(FPDF_ANNOTATION_SUBTYPE subtype) { + // The supported subtypes must also be communicated in the user doc. + return subtype == FPDF_ANNOT_INK || subtype == FPDF_ANNOT_STAMP; +} + +FPDF_EXPORT FPDF_BOOL FPDF_CALLCONV +FPDFAnnot_UpdateObject(FPDF_ANNOTATION annot, FPDF_PAGEOBJECT obj) { + CPDF_AnnotContext* pAnnot = CPDFAnnotContextFromFPDFAnnotation(annot); + CPDF_PageObject* pObj = CPDFPageObjectFromFPDFPageObject(obj); + if (!pAnnot || !pAnnot->GetAnnotDict() || !pAnnot->HasForm() || !pObj) + return false; + + // Check that the annotation type is supported by this method. + if (!FPDFAnnot_IsObjectSupportedSubtype(FPDFAnnot_GetSubtype(annot))) + return false; + + // Check that the annotation already has an appearance stream, since an + // existing object is to be updated. + CPDF_Stream* pStream = FPDFDOC_GetAnnotAP(pAnnot->GetAnnotDict(), + CPDF_Annot::AppearanceMode::Normal); + if (!pStream) + return false; + + // Check that the object is already in this annotation's object list. + CPDF_Form* pForm = pAnnot->GetForm(); + CPDF_PageObjectList* pObjList = pForm->GetPageObjectList(); + auto it = + std::find_if(pObjList->begin(), pObjList->end(), + [pObj](const std::unique_ptr& candidate) { + return candidate.get() == pObj; + }); + if (it == pObjList->end()) + return false; + + // Update the content stream data in the annotation's AP stream. + UpdateContentStream(pForm, pStream); + return true; +} + +FPDF_EXPORT FPDF_BOOL FPDF_CALLCONV +FPDFAnnot_AppendObject(FPDF_ANNOTATION annot, FPDF_PAGEOBJECT obj) { + CPDF_AnnotContext* pAnnot = CPDFAnnotContextFromFPDFAnnotation(annot); + CPDF_PageObject* pObj = CPDFPageObjectFromFPDFPageObject(obj); + if (!pAnnot || !pObj) + return false; + + CPDF_Dictionary* pAnnotDict = pAnnot->GetAnnotDict(); + CPDF_Page* pPage = pAnnot->GetPage(); + if (!pAnnotDict || !pPage) + return false; + + // Check that the annotation type is supported by this method. + if (!FPDFAnnot_IsObjectSupportedSubtype(FPDFAnnot_GetSubtype(annot))) + return false; + + // If the annotation does not have an AP stream yet, generate and set it. + CPDF_Stream* pStream = FPDFDOC_GetAnnotAP(pAnnot->GetAnnotDict(), + CPDF_Annot::AppearanceMode::Normal); + if (!pStream) { + CPVT_GenerateAP::GenerateEmptyAP(pPage->m_pDocument.Get(), pAnnotDict); + pStream = + FPDFDOC_GetAnnotAP(pAnnotDict, CPDF_Annot::AppearanceMode::Normal); + if (!pStream) + return false; + } + + // Get the annotation's corresponding form object for parsing its AP stream. + if (!pAnnot->HasForm()) + pAnnot->SetForm(pStream); + + // Check that the object did not come from the same annotation. If this check + // succeeds, then it is assumed that the object came from + // FPDFPageObj_CreateNew{Path|Rect}() or FPDFPageObj_New{Text|Image}Obj(). + // Note that an object that came from a different annotation must not be + // passed here, since an object cannot belong to more than one annotation. + CPDF_Form* pForm = pAnnot->GetForm(); + CPDF_PageObjectList* pObjList = pForm->GetPageObjectList(); + auto it = + std::find_if(pObjList->begin(), pObjList->end(), + [pObj](const std::unique_ptr& candidate) { + return candidate.get() == pObj; + }); + if (it != pObjList->end()) + return false; + + // Append the object to the object list. + std::unique_ptr pPageObjHolder(pObj); + pObjList->push_back(std::move(pPageObjHolder)); + + // Set the content stream data in the annotation's AP stream. + UpdateContentStream(pForm, pStream); + return true; +} + +FPDF_EXPORT int FPDF_CALLCONV FPDFAnnot_GetObjectCount(FPDF_ANNOTATION annot) { + CPDF_AnnotContext* pAnnot = CPDFAnnotContextFromFPDFAnnotation(annot); + if (!pAnnot || !pAnnot->GetAnnotDict()) + return 0; + + if (!pAnnot->HasForm()) { + CPDF_Stream* pStream = FPDFDOC_GetAnnotAP( + pAnnot->GetAnnotDict(), CPDF_Annot::AppearanceMode::Normal); + if (!pStream) + return 0; + + pAnnot->SetForm(pStream); + } + return pdfium::CollectionSize(*pAnnot->GetForm()->GetPageObjectList()); +} + +FPDF_EXPORT FPDF_PAGEOBJECT FPDF_CALLCONV +FPDFAnnot_GetObject(FPDF_ANNOTATION annot, int index) { + CPDF_AnnotContext* pAnnot = CPDFAnnotContextFromFPDFAnnotation(annot); + if (!pAnnot || !pAnnot->GetAnnotDict() || index < 0) + return nullptr; + + if (!pAnnot->HasForm()) { + CPDF_Stream* pStream = FPDFDOC_GetAnnotAP( + pAnnot->GetAnnotDict(), CPDF_Annot::AppearanceMode::Normal); + if (!pStream) + return nullptr; + + pAnnot->SetForm(pStream); + } + + return pAnnot->GetForm()->GetPageObjectList()->GetPageObjectByIndex(index); +} + +FPDF_EXPORT FPDF_BOOL FPDF_CALLCONV +FPDFAnnot_RemoveObject(FPDF_ANNOTATION annot, int index) { + CPDF_AnnotContext* pAnnot = CPDFAnnotContextFromFPDFAnnotation(annot); + if (!pAnnot || !pAnnot->GetAnnotDict() || !pAnnot->HasForm() || index < 0) + return false; + + // Check that the annotation type is supported by this method. + if (!FPDFAnnot_IsObjectSupportedSubtype(FPDFAnnot_GetSubtype(annot))) + return false; + + // Check that the annotation already has an appearance stream, since an + // existing object is to be deleted. + CPDF_Stream* pStream = FPDFDOC_GetAnnotAP(pAnnot->GetAnnotDict(), + CPDF_Annot::AppearanceMode::Normal); + if (!pStream) + return false; + + CPDF_PageObjectList* pObjList = pAnnot->GetForm()->GetPageObjectList(); + if (static_cast(index) >= pObjList->size()) + return false; + + pObjList->erase(pObjList->begin() + index); + UpdateContentStream(pAnnot->GetForm(), pStream); + return true; +} + +FPDF_EXPORT FPDF_BOOL FPDF_CALLCONV FPDFAnnot_SetColor(FPDF_ANNOTATION annot, + FPDFANNOT_COLORTYPE type, + unsigned int R, + unsigned int G, + unsigned int B, + unsigned int A) { + if (!annot || R > 255 || G > 255 || B > 255 || A > 255) + return false; + + CPDF_Dictionary* pAnnotDict = + CPDFAnnotContextFromFPDFAnnotation(annot)->GetAnnotDict(); + if (!pAnnotDict) + return false; + + // For annotations with their appearance streams already defined, the path + // stream's own color definitions take priority over the annotation color + // definitions set by this method, hence this method will simply fail. + if (HasAPStream(pAnnotDict)) + return false; + + // Set the opacity of the annotation. + pAnnotDict->SetNewFor("CA", A / 255.f); + + // Set the color of the annotation. + ByteString key = type == FPDFANNOT_COLORTYPE_InteriorColor ? "IC" : "C"; + CPDF_Array* pColor = pAnnotDict->GetArrayFor(key); + if (pColor) + pColor->Clear(); + else + pColor = pAnnotDict->SetNewFor(key); + + pColor->AddNew(R / 255.f); + pColor->AddNew(G / 255.f); + pColor->AddNew(B / 255.f); + + return true; +} + +FPDF_EXPORT FPDF_BOOL FPDF_CALLCONV FPDFAnnot_GetColor(FPDF_ANNOTATION annot, + FPDFANNOT_COLORTYPE type, + unsigned int* R, + unsigned int* G, + unsigned int* B, + unsigned int* A) { + if (!annot || !R || !G || !B || !A) + return false; + + CPDF_Dictionary* pAnnotDict = + CPDFAnnotContextFromFPDFAnnotation(annot)->GetAnnotDict(); + if (!pAnnotDict) + return false; + + // For annotations with their appearance streams already defined, the path + // stream's own color definitions take priority over the annotation color + // definitions retrieved by this method, hence this method will simply fail. + if (HasAPStream(pAnnotDict)) + return false; + + CPDF_Array* pColor = pAnnotDict->GetArrayFor( + type == FPDFANNOT_COLORTYPE_InteriorColor ? "IC" : "C"); + *A = + (pAnnotDict->KeyExist("CA") ? pAnnotDict->GetNumberFor("CA") : 1) * 255.f; + if (!pColor) { + // Use default color. The default colors must be consistent with the ones + // used to generate AP. See calls to GetColorStringWithDefault() in + // CPVT_GenerateAP::Generate*AP(). + if (pAnnotDict->GetStringFor("Subtype") == "Highlight") { + *R = 255; + *G = 255; + *B = 0; + } else { + *R = 0; + *G = 0; + *B = 0; + } + return true; + } + + CFX_Color color = CFX_Color::ParseColor(*pColor); + switch (color.nColorType) { + case CFX_Color::kRGB: + *R = color.fColor1 * 255.f; + *G = color.fColor2 * 255.f; + *B = color.fColor3 * 255.f; + break; + case CFX_Color::kGray: + *R = 255.f * color.fColor1; + *G = 255.f * color.fColor1; + *B = 255.f * color.fColor1; + break; + case CFX_Color::kCMYK: + *R = 255.f * (1 - color.fColor1) * (1 - color.fColor4); + *G = 255.f * (1 - color.fColor2) * (1 - color.fColor4); + *B = 255.f * (1 - color.fColor3) * (1 - color.fColor4); + break; + case CFX_Color::kTransparent: + *R = 0; + *G = 0; + *B = 0; + break; + } + return true; +} + +FPDF_EXPORT FPDF_BOOL FPDF_CALLCONV +FPDFAnnot_HasAttachmentPoints(FPDF_ANNOTATION annot) { + if (!annot) + return false; + + FPDF_ANNOTATION_SUBTYPE subtype = FPDFAnnot_GetSubtype(annot); + return subtype == FPDF_ANNOT_LINK || subtype == FPDF_ANNOT_HIGHLIGHT || + subtype == FPDF_ANNOT_UNDERLINE || subtype == FPDF_ANNOT_SQUIGGLY || + subtype == FPDF_ANNOT_STRIKEOUT; +} + +FPDF_EXPORT FPDF_BOOL FPDF_CALLCONV +FPDFAnnot_SetAttachmentPoints(FPDF_ANNOTATION annot, + const FS_QUADPOINTSF* quad_points) { + if (!FPDFAnnot_HasAttachmentPoints(annot) || !quad_points) + return false; + + CPDF_Dictionary* pAnnotDict = + CPDFAnnotContextFromFPDFAnnotation(annot)->GetAnnotDict(); + if (!pAnnotDict) + return false; + + // Update the "QuadPoints" entry in the annotation dictionary. + CPDF_Array* pQuadPoints = pAnnotDict->GetArrayFor("QuadPoints"); + if (pQuadPoints) + pQuadPoints->Clear(); + else + pQuadPoints = pAnnotDict->SetNewFor("QuadPoints"); + + pQuadPoints->AddNew(quad_points->x1); + pQuadPoints->AddNew(quad_points->y1); + pQuadPoints->AddNew(quad_points->x2); + pQuadPoints->AddNew(quad_points->y2); + pQuadPoints->AddNew(quad_points->x3); + pQuadPoints->AddNew(quad_points->y3); + pQuadPoints->AddNew(quad_points->x4); + pQuadPoints->AddNew(quad_points->y4); + + // If the annotation's appearance stream is defined, and the new quadpoints + // defines a bigger bounding box than the appearance stream currently + // specifies, then update the "BBox" entry in the AP dictionary too, since it + // comes from annotation dictionary's "QuadPoints" entry. + CPDF_Stream* pStream = + FPDFDOC_GetAnnotAP(pAnnotDict, CPDF_Annot::AppearanceMode::Normal); + if (pStream) { + CFX_FloatRect newRect = CPDF_Annot::BoundingRectFromQuadPoints(pAnnotDict); + if (newRect.Contains(pStream->GetDict()->GetRectFor("BBox"))) + pStream->GetDict()->SetRectFor("BBox", newRect); + } + return true; +} + +FPDF_EXPORT size_t FPDF_CALLCONV +FPDFAnnot_CountAttachmentPoints(FPDF_ANNOTATION annot) { + if (!FPDFAnnot_HasAttachmentPoints(annot)) + return 0; + + CPDF_Dictionary* pAnnotDict = + CPDFAnnotContextFromFPDFAnnotation(annot)->GetAnnotDict(); + const CPDF_Array* pArray = GetQuadPointsArrayFromDictionary(pAnnotDict); + return pArray ? pArray->GetCount() / 8 : 0; +} + +FPDF_EXPORT FPDF_BOOL FPDF_CALLCONV +FPDFAnnot_GetAttachmentPoints(FPDF_ANNOTATION annot, + FS_QUADPOINTSF* quad_points) { + if (!FPDFAnnot_HasAttachmentPoints(annot) || !quad_points) + return false; + + return GetQuadPointsFromDictionary( + CPDFAnnotContextFromFPDFAnnotation(annot)->GetAnnotDict(), 0, + quad_points); +} + +FPDF_EXPORT FPDF_BOOL FPDF_CALLCONV FPDFAnnot_SetRect(FPDF_ANNOTATION annot, + const FS_RECTF* rect) { + if (!annot || !rect) + return false; + + CPDF_Dictionary* pAnnotDict = + CPDFAnnotContextFromFPDFAnnotation(annot)->GetAnnotDict(); + if (!pAnnotDict) + return false; + + CFX_FloatRect newRect = CFXFloatRectFromFSRECTF(*rect); + + // Update the "Rect" entry in the annotation dictionary. + pAnnotDict->SetRectFor("Rect", newRect); + + // If the annotation's appearance stream is defined, the annotation is of a + // type that does not have quadpoints, and the new rectangle is bigger than + // the current bounding box, then update the "BBox" entry in the AP + // dictionary too, since its "BBox" entry comes from annotation dictionary's + // "Rect" entry. + if (FPDFAnnot_HasAttachmentPoints(annot)) + return true; + + CPDF_Stream* pStream = + FPDFDOC_GetAnnotAP(pAnnotDict, CPDF_Annot::AppearanceMode::Normal); + if (pStream && newRect.Contains(pStream->GetDict()->GetRectFor("BBox"))) + pStream->GetDict()->SetRectFor("BBox", newRect); + return true; +} + +FPDF_EXPORT FPDF_BOOL FPDF_CALLCONV FPDFAnnot_GetRect(FPDF_ANNOTATION annot, + FS_RECTF* rect) { + if (!annot || !rect) + return false; + + CPDF_Dictionary* pAnnotDict = + CPDFAnnotContextFromFPDFAnnotation(annot)->GetAnnotDict(); + if (!pAnnotDict) + return false; + + FSRECTFFromCFXFloatRect(pAnnotDict->GetRectFor("Rect"), rect); + return true; +} + +FPDF_EXPORT FPDF_BOOL FPDF_CALLCONV FPDFAnnot_HasKey(FPDF_ANNOTATION annot, + FPDF_BYTESTRING key) { + if (!annot) + return false; + + CPDF_Dictionary* pAnnotDict = + CPDFAnnotContextFromFPDFAnnotation(annot)->GetAnnotDict(); + if (!pAnnotDict) + return false; + + return pAnnotDict->KeyExist(key); +} + +FPDF_EXPORT FPDF_OBJECT_TYPE FPDF_CALLCONV +FPDFAnnot_GetValueType(FPDF_ANNOTATION annot, FPDF_BYTESTRING key) { + if (!FPDFAnnot_HasKey(annot, key)) + return FPDF_OBJECT_UNKNOWN; + + auto* pAnnot = CPDFAnnotContextFromFPDFAnnotation(annot); + CPDF_Object* pObj = pAnnot->GetAnnotDict()->GetObjectFor(key); + return pObj ? pObj->GetType() : FPDF_OBJECT_UNKNOWN; +} + +FPDF_EXPORT FPDF_BOOL FPDF_CALLCONV +FPDFAnnot_SetStringValue(FPDF_ANNOTATION annot, + FPDF_BYTESTRING key, + FPDF_WIDESTRING value) { + if (!annot) + return false; + + CPDF_Dictionary* pAnnotDict = + CPDFAnnotContextFromFPDFAnnotation(annot)->GetAnnotDict(); + if (!pAnnotDict) + return false; + + pAnnotDict->SetNewFor( + key, CFXByteStringFromFPDFWideString(value), false); + return true; +} + +FPDF_EXPORT unsigned long FPDF_CALLCONV +FPDFAnnot_GetStringValue(FPDF_ANNOTATION annot, + FPDF_BYTESTRING key, + void* buffer, + unsigned long buflen) { + if (!annot) + return 0; + + CPDF_Dictionary* pAnnotDict = + CPDFAnnotContextFromFPDFAnnotation(annot)->GetAnnotDict(); + if (!pAnnotDict) + return 0; + + return Utf16EncodeMaybeCopyAndReturnLength(pAnnotDict->GetUnicodeTextFor(key), + buffer, buflen); +} + +FPDF_EXPORT FPDF_BOOL FPDF_CALLCONV +FPDFAnnot_SetAP(FPDF_ANNOTATION annot, + FPDF_ANNOT_APPEARANCEMODE appearanceMode, + FPDF_WIDESTRING value) { + if (appearanceMode < 0 || appearanceMode >= FPDF_ANNOT_APPEARANCEMODE_COUNT) + return false; + + if (!annot) + return false; + + CPDF_Dictionary* pAnnotDict = + CPDFAnnotContextFromFPDFAnnotation(annot)->GetAnnotDict(); + if (!pAnnotDict) + return false; + + constexpr const char* modeKeyForMode[] = {"N", "R", "D"}; + static_assert(FX_ArraySize(modeKeyForMode) == FPDF_ANNOT_APPEARANCEMODE_COUNT, + "length of modeKeyForMode should be equal to " + "FPDF_ANNOT_APPEARANCEMODE_COUNT"); + const char* modeKey = modeKeyForMode[appearanceMode]; + + CPDF_Dictionary* pApDict = pAnnotDict->GetDictFor("AP"); + + // If value is null, we're in remove mode. Otherwise, we're in add/update + // mode. + if (value) { + if (!pApDict) + pApDict = pAnnotDict->SetNewFor("AP"); + + ByteString newValue = CFXByteStringFromFPDFWideString(value); + auto pNewApStream = pdfium::MakeUnique(); + pNewApStream->SetData(newValue.raw_str(), newValue.GetLength()); + pApDict->SetFor(modeKey, std::move(pNewApStream)); + } else { + if (pApDict) { + if (appearanceMode == FPDF_ANNOT_APPEARANCEMODE_NORMAL) + pAnnotDict->RemoveFor("AP"); + else + pApDict->RemoveFor(modeKey); + } + } + + return true; +} + +FPDF_EXPORT unsigned long FPDF_CALLCONV +FPDFAnnot_GetAP(FPDF_ANNOTATION annot, + FPDF_ANNOT_APPEARANCEMODE appearanceMode, + void* buffer, + unsigned long buflen) { + if (appearanceMode < 0 || appearanceMode >= FPDF_ANNOT_APPEARANCEMODE_COUNT) + return 0; + + if (!annot) + return 0; + + CPDF_Dictionary* pAnnotDict = + CPDFAnnotContextFromFPDFAnnotation(annot)->GetAnnotDict(); + if (!pAnnotDict) + return 0; + + CPDF_Annot::AppearanceMode mode = + static_cast(appearanceMode); + + CPDF_Stream* pStream = FPDFDOC_GetAnnotAPNoFallback(pAnnotDict, mode); + return Utf16EncodeMaybeCopyAndReturnLength( + pStream ? pStream->GetUnicodeText() : L"", buffer, buflen); +} + +FPDF_EXPORT FPDF_ANNOTATION FPDF_CALLCONV +FPDFAnnot_GetLinkedAnnot(FPDF_ANNOTATION annot, FPDF_BYTESTRING key) { + CPDF_AnnotContext* pAnnot = CPDFAnnotContextFromFPDFAnnotation(annot); + if (!pAnnot || !pAnnot->GetAnnotDict()) + return nullptr; + + CPDF_Dictionary* pLinkedDict = pAnnot->GetAnnotDict()->GetDictFor(key); + if (!pLinkedDict || pLinkedDict->GetStringFor("Type") != "Annot") + return nullptr; + + auto pLinkedAnnot = pdfium::MakeUnique( + pLinkedDict, pAnnot->GetPage(), nullptr); + return pLinkedAnnot.release(); +} + +FPDF_EXPORT int FPDF_CALLCONV FPDFAnnot_GetFlags(FPDF_ANNOTATION annot) { + if (!annot) + return FPDF_ANNOT_FLAG_NONE; + + CPDF_Dictionary* pAnnotDict = + CPDFAnnotContextFromFPDFAnnotation(annot)->GetAnnotDict(); + return pAnnotDict ? pAnnotDict->GetIntegerFor("F") : FPDF_ANNOT_FLAG_NONE; +} + +FPDF_EXPORT FPDF_BOOL FPDF_CALLCONV FPDFAnnot_SetFlags(FPDF_ANNOTATION annot, + int flags) { + if (!annot) + return false; + + CPDF_Dictionary* pAnnotDict = + CPDFAnnotContextFromFPDFAnnotation(annot)->GetAnnotDict(); + if (!pAnnotDict) + return false; + + pAnnotDict->SetNewFor("F", flags); + return true; +} + +FPDF_EXPORT int FPDF_CALLCONV +FPDFAnnot_GetFormFieldFlags(FPDF_PAGE page, FPDF_ANNOTATION annot) { + CPDF_Page* pPage = CPDFPageFromFPDFPage(page); + if (!pPage || !annot) + return FPDF_FORMFLAG_NONE; + + CPDF_Dictionary* pAnnotDict = + CPDFAnnotContextFromFPDFAnnotation(annot)->GetAnnotDict(); + if (!pAnnotDict) + return FPDF_FORMFLAG_NONE; + + CPDF_InterForm interform(pPage->m_pDocument.Get()); + CPDF_FormField* pFormField = interform.GetFieldByDict(pAnnotDict); + return pFormField ? pFormField->GetFieldFlags() : FPDF_FORMFLAG_NONE; +} + +FPDF_EXPORT FPDF_ANNOTATION FPDF_CALLCONV +FPDFAnnot_GetFormFieldAtPoint(FPDF_FORMHANDLE hHandle, + FPDF_PAGE page, + double page_x, + double page_y) { + CPDF_Page* pPage = CPDFPageFromFPDFPage(page); + if (!hHandle || !pPage) + return nullptr; + + CPDF_InterForm interform(pPage->m_pDocument.Get()); + int annot_index = -1; + CPDF_FormControl* pFormCtrl = interform.GetControlAtPoint( + pPage, CFX_PointF(static_cast(page_x), static_cast(page_y)), + &annot_index); + if (!pFormCtrl || annot_index == -1) + return nullptr; + return FPDFPage_GetAnnot(page, annot_index); +} diff --git a/fpdfsdk/fpdf_annot_embeddertest.cpp b/fpdfsdk/fpdf_annot_embeddertest.cpp new file mode 100644 index 0000000000..b97193db46 --- /dev/null +++ b/fpdfsdk/fpdf_annot_embeddertest.cpp @@ -0,0 +1,1458 @@ +// Copyright 2017 PDFium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include +#include +#include +#include + +#include "core/fxcrt/fx_system.h" +#include "public/cpp/fpdf_deleters.h" +#include "public/fpdf_annot.h" +#include "public/fpdf_edit.h" +#include "public/fpdfview.h" +#include "testing/embedder_test.h" +#include "testing/gmock/include/gmock/gmock-matchers.h" +#include "testing/gtest/include/gtest/gtest.h" + +static constexpr char kContentsKey[] = "Contents"; + +class FPDFAnnotEmbeddertest : public EmbedderTest {}; + +std::wstring BufferToWString(const std::vector& buf) { + return GetPlatformWString(reinterpret_cast(buf.data())); +} + +std::string BufferToString(const std::vector& buf) { + return GetPlatformString(reinterpret_cast(buf.data())); +} + +TEST_F(FPDFAnnotEmbeddertest, RenderAnnotWithOnlyRolloverAP) { + // Open a file with one annotation and load its first page. + ASSERT_TRUE(OpenDocument("annotation_highlight_rollover_ap.pdf")); + FPDF_PAGE page = LoadPage(0); + ASSERT_TRUE(page); + + // This annotation has a malformed appearance stream, which does not have its + // normal appearance defined, only its rollover appearance. In this case, its + // normal appearance should be generated, allowing the highlight annotation to + // still display. + std::unique_ptr bitmap = + RenderLoadedPageWithFlags(page, FPDF_ANNOT); + CompareBitmap(bitmap.get(), 612, 792, "dc98f06da047bd8aabfa99562d2cbd1e"); + + UnloadPage(page); +} + +TEST_F(FPDFAnnotEmbeddertest, RenderMultilineMarkupAnnotWithoutAP) { + const char md5_hash[] = "76512832d88017668d9acc7aacd13dae"; + // Open a file with multiline markup annotations. + ASSERT_TRUE(OpenDocument("annotation_markup_multiline_no_ap.pdf")); + FPDF_PAGE page = LoadPage(0); + ASSERT_TRUE(page); + + std::unique_ptr bitmap = + RenderLoadedPageWithFlags(page, FPDF_ANNOT); + CompareBitmap(bitmap.get(), 595, 842, md5_hash); + + UnloadPage(page); +} + +TEST_F(FPDFAnnotEmbeddertest, ExtractHighlightLongContent) { + // Open a file with one annotation and load its first page. + ASSERT_TRUE(OpenDocument("annotation_highlight_long_content.pdf")); + // TODO(thestig): This test should use LoadPage() and UnloadPage(), but one of + // the FORM API calls in LoadPage() makes this test fail. So use + // FPDF_LoadPage() and FPDF_ClosePage() for now. + FPDF_PAGE page = FPDF_LoadPage(document(), 0); + ASSERT_TRUE(page); + + // Check that there is a total of 1 annotation on its first page. + EXPECT_EQ(1, FPDFPage_GetAnnotCount(page)); + + // Check that the annotation is of type "highlight". + { + std::unique_ptr annot( + FPDFPage_GetAnnot(page, 0)); + ASSERT_TRUE(annot); + EXPECT_EQ(FPDF_ANNOT_HIGHLIGHT, FPDFAnnot_GetSubtype(annot.get())); + + // Check that the annotation color is yellow. + unsigned int R; + unsigned int G; + unsigned int B; + unsigned int A; + ASSERT_TRUE(FPDFAnnot_GetColor(annot.get(), FPDFANNOT_COLORTYPE_Color, &R, + &G, &B, &A)); + EXPECT_EQ(255u, R); + EXPECT_EQ(255u, G); + EXPECT_EQ(0u, B); + EXPECT_EQ(255u, A); + + // Check that the author is correct. + static constexpr char kAuthorKey[] = "T"; + EXPECT_EQ(FPDF_OBJECT_STRING, + FPDFAnnot_GetValueType(annot.get(), kAuthorKey)); + unsigned long len = + FPDFAnnot_GetStringValue(annot.get(), kAuthorKey, nullptr, 0); + std::vector buf(len); + EXPECT_EQ(28u, FPDFAnnot_GetStringValue(annot.get(), kAuthorKey, buf.data(), + len)); + EXPECT_STREQ(L"Jae Hyun Park", BufferToWString(buf).c_str()); + + // Check that the content is correct. + EXPECT_EQ(FPDF_OBJECT_STRING, + FPDFAnnot_GetValueType(annot.get(), kContentsKey)); + len = FPDFAnnot_GetStringValue(annot.get(), kContentsKey, nullptr, 0); + buf.clear(); + buf.resize(len); + EXPECT_EQ(2690u, FPDFAnnot_GetStringValue(annot.get(), kContentsKey, + buf.data(), len)); + const wchar_t contents[] = + L"This is a note for that highlight annotation. Very long highlight " + "annotation. Long long long Long long longLong long longLong long " + "longLong long longLong long longLong long longLong long longLong long " + "longLong long longLong long longLong long longLong long longLong long " + "longLong long longLong long longLong long longLong long longLong long " + "longLong long longLong long longLong long longLong long longLong long " + "longLong long longLong long longLong long longLong long longLong long " + "longLong long longLong long longLong long longLong long longLong long " + "longLong long longLong long longLong long longLong long longLong long " + "longLong long longLong long longLong long longLong long longLong long " + "longLong long longLong long longLong long longLong long longLong long " + "longLong long longLong long longLong long longLong long longLong long " + "longLong long longLong long longLong long longLong long longLong long " + "longLong long longLong long longLong long longLong long longLong long " + "longLong long longLong long longLong long longLong long longLong long " + "longLong long longLong long longLong long longLong long longLong long " + "longLong long longLong long longLong long longLong long longLong long " + "longLong long longLong long longLong long longLong long longLong long " + "longLong long longLong long longLong long longLong long longLong long " + "longLong long long. END"; + EXPECT_STREQ(contents, BufferToWString(buf).c_str()); + + // Check that the quadpoints are correct. + FS_QUADPOINTSF quadpoints; + ASSERT_TRUE(FPDFAnnot_GetAttachmentPoints(annot.get(), &quadpoints)); + EXPECT_EQ(115.802643f, quadpoints.x1); + EXPECT_EQ(718.913940f, quadpoints.y1); + EXPECT_EQ(157.211182f, quadpoints.x4); + EXPECT_EQ(706.264465f, quadpoints.y4); + } + FPDF_ClosePage(page); +} + +TEST_F(FPDFAnnotEmbeddertest, ExtractInkMultiple) { + // Open a file with three annotations and load its first page. + ASSERT_TRUE(OpenDocument("annotation_ink_multiple.pdf")); + // TODO(thestig): This test should use LoadPage() and UnloadPage(), but one of + // the FORM API calls in LoadPage() makes this test fail. So use + // FPDF_LoadPage() and FPDF_ClosePage() for now. + FPDF_PAGE page = FPDF_LoadPage(document(), 0); + ASSERT_TRUE(page); + + // Check that there is a total of 3 annotation on its first page. + EXPECT_EQ(3, FPDFPage_GetAnnotCount(page)); + + { + // Check that the third annotation is of type "ink". + std::unique_ptr annot( + FPDFPage_GetAnnot(page, 2)); + ASSERT_TRUE(annot); + EXPECT_EQ(FPDF_ANNOT_INK, FPDFAnnot_GetSubtype(annot.get())); + + // Check that the annotation color is blue with opacity. + unsigned int R; + unsigned int G; + unsigned int B; + unsigned int A; + ASSERT_TRUE(FPDFAnnot_GetColor(annot.get(), FPDFANNOT_COLORTYPE_Color, &R, + &G, &B, &A)); + EXPECT_EQ(0u, R); + EXPECT_EQ(0u, G); + EXPECT_EQ(255u, B); + EXPECT_EQ(76u, A); + + // Check that there is no content. + EXPECT_EQ(2u, + FPDFAnnot_GetStringValue(annot.get(), kContentsKey, nullptr, 0)); + + // Check that the rectange coordinates are correct. + // Note that upon rendering, the rectangle coordinates will be adjusted. + FS_RECTF rect; + ASSERT_TRUE(FPDFAnnot_GetRect(annot.get(), &rect)); + EXPECT_EQ(351.820404f, rect.left); + EXPECT_EQ(583.830688f, rect.bottom); + EXPECT_EQ(475.336090f, rect.right); + EXPECT_EQ(681.535034f, rect.top); + } + FPDF_ClosePage(page); +} + +TEST_F(FPDFAnnotEmbeddertest, AddIllegalSubtypeAnnotation) { + // Open a file with one annotation and load its first page. + ASSERT_TRUE(OpenDocument("annotation_highlight_long_content.pdf")); + FPDF_PAGE page = LoadPage(0); + ASSERT_TRUE(page); + + // Add an annotation with an illegal subtype. + ASSERT_FALSE(FPDFPage_CreateAnnot(page, -1)); + + UnloadPage(page); +} + +TEST_F(FPDFAnnotEmbeddertest, AddFirstTextAnnotation) { + // Open a file with no annotation and load its first page. + ASSERT_TRUE(OpenDocument("hello_world.pdf")); + FPDF_PAGE page = LoadPage(0); + ASSERT_TRUE(page); + EXPECT_EQ(0, FPDFPage_GetAnnotCount(page)); + + { + // Add a text annotation to the page. + std::unique_ptr annot( + FPDFPage_CreateAnnot(page, FPDF_ANNOT_TEXT)); + ASSERT_TRUE(annot); + + // Check that there is now 1 annotations on this page. + EXPECT_EQ(1, FPDFPage_GetAnnotCount(page)); + + // Check that the subtype of the annotation is correct. + EXPECT_EQ(FPDF_ANNOT_TEXT, FPDFAnnot_GetSubtype(annot.get())); + } + + { + std::unique_ptr annot( + FPDFPage_GetAnnot(page, 0)); + ASSERT_TRUE(annot); + EXPECT_EQ(FPDF_ANNOT_TEXT, FPDFAnnot_GetSubtype(annot.get())); + + // Set the color of the annotation. + ASSERT_TRUE(FPDFAnnot_SetColor(annot.get(), FPDFANNOT_COLORTYPE_Color, 51, + 102, 153, 204)); + // Check that the color has been set correctly. + unsigned int R; + unsigned int G; + unsigned int B; + unsigned int A; + ASSERT_TRUE(FPDFAnnot_GetColor(annot.get(), FPDFANNOT_COLORTYPE_Color, &R, + &G, &B, &A)); + EXPECT_EQ(51u, R); + EXPECT_EQ(102u, G); + EXPECT_EQ(153u, B); + EXPECT_EQ(204u, A); + + // Change the color of the annotation. + ASSERT_TRUE(FPDFAnnot_SetColor(annot.get(), FPDFANNOT_COLORTYPE_Color, 204, + 153, 102, 51)); + // Check that the color has been set correctly. + ASSERT_TRUE(FPDFAnnot_GetColor(annot.get(), FPDFANNOT_COLORTYPE_Color, &R, + &G, &B, &A)); + EXPECT_EQ(204u, R); + EXPECT_EQ(153u, G); + EXPECT_EQ(102u, B); + EXPECT_EQ(51u, A); + + // Set the annotation rectangle. + FS_RECTF rect; + ASSERT_TRUE(FPDFAnnot_GetRect(annot.get(), &rect)); + EXPECT_EQ(0.f, rect.left); + EXPECT_EQ(0.f, rect.right); + rect.left = 35; + rect.bottom = 150; + rect.right = 53; + rect.top = 165; + ASSERT_TRUE(FPDFAnnot_SetRect(annot.get(), &rect)); + // Check that the annotation rectangle has been set correctly. + ASSERT_TRUE(FPDFAnnot_GetRect(annot.get(), &rect)); + EXPECT_EQ(35.f, rect.left); + EXPECT_EQ(150.f, rect.bottom); + EXPECT_EQ(53.f, rect.right); + EXPECT_EQ(165.f, rect.top); + + // Set the content of the annotation. + static constexpr wchar_t kContents[] = + L"Hello! This is a customized content."; + std::unique_ptr text = + GetFPDFWideString(kContents); + ASSERT_TRUE( + FPDFAnnot_SetStringValue(annot.get(), kContentsKey, text.get())); + // Check that the content has been set correctly. + unsigned long len = + FPDFAnnot_GetStringValue(annot.get(), kContentsKey, nullptr, 0); + std::vector buf(len); + EXPECT_EQ(74u, FPDFAnnot_GetStringValue(annot.get(), kContentsKey, + buf.data(), len)); + EXPECT_STREQ(kContents, BufferToWString(buf).c_str()); + } + UnloadPage(page); +} + +TEST_F(FPDFAnnotEmbeddertest, AddAndSaveUnderlineAnnotation) { + // Open a file with one annotation and load its first page. + ASSERT_TRUE(OpenDocument("annotation_highlight_long_content.pdf")); + FPDF_PAGE page = LoadPage(0); + ASSERT_TRUE(page); + + // Check that there is a total of one annotation on its first page, and verify + // its quadpoints. + EXPECT_EQ(1, FPDFPage_GetAnnotCount(page)); + FS_QUADPOINTSF quadpoints; + { + std::unique_ptr annot( + FPDFPage_GetAnnot(page, 0)); + ASSERT_TRUE(annot); + ASSERT_TRUE(FPDFAnnot_GetAttachmentPoints(annot.get(), &quadpoints)); + EXPECT_EQ(115.802643f, quadpoints.x1); + EXPECT_EQ(718.913940f, quadpoints.y1); + EXPECT_EQ(157.211182f, quadpoints.x4); + EXPECT_EQ(706.264465f, quadpoints.y4); + } + + // Add an underline annotation to the page and set its quadpoints. + { + std::unique_ptr annot( + FPDFPage_CreateAnnot(page, FPDF_ANNOT_UNDERLINE)); + ASSERT_TRUE(annot); + quadpoints.x1 = 140.802643f; + quadpoints.x3 = 140.802643f; + ASSERT_TRUE(FPDFAnnot_SetAttachmentPoints(annot.get(), &quadpoints)); + } + + // Save the document, closing the page and document. + EXPECT_TRUE(FPDF_SaveAsCopy(document(), this, 0)); + UnloadPage(page); + + // Open the saved document. + const char md5[] = "dba153419f67b7c0c0e3d22d3e8910d5"; + + OpenSavedDocument(); + page = LoadSavedPage(0); + VerifySavedRendering(page, 612, 792, md5); + + // Check that the saved document has 2 annotations on the first page + EXPECT_EQ(2, FPDFPage_GetAnnotCount(page)); + + { + // Check that the second annotation is an underline annotation and verify + // its quadpoints. + std::unique_ptr new_annot( + FPDFPage_GetAnnot(page, 1)); + ASSERT_TRUE(new_annot); + EXPECT_EQ(FPDF_ANNOT_UNDERLINE, FPDFAnnot_GetSubtype(new_annot.get())); + FS_QUADPOINTSF new_quadpoints; + ASSERT_TRUE( + FPDFAnnot_GetAttachmentPoints(new_annot.get(), &new_quadpoints)); + EXPECT_NEAR(quadpoints.x1, new_quadpoints.x1, 0.001f); + EXPECT_NEAR(quadpoints.y1, new_quadpoints.y1, 0.001f); + EXPECT_NEAR(quadpoints.x4, new_quadpoints.x4, 0.001f); + EXPECT_NEAR(quadpoints.y4, new_quadpoints.y4, 0.001f); + } + + CloseSavedPage(page); + CloseSavedDocument(); +} + +TEST_F(FPDFAnnotEmbeddertest, ModifyRectQuadpointsWithAP) { +#if _FX_PLATFORM_ == _FX_PLATFORM_APPLE_ + const char md5_original[] = "63af8432fab95a67cdebb7cd0e514941"; + const char md5_modified_highlight[] = "aec26075011349dec9bace891856b5f2"; + const char md5_modified_square[] = "057f57a32be95975775e5ec513fdcb56"; +#elif _FX_PLATFORM_ == _FX_PLATFORM_WINDOWS_ + const char md5_original[] = "0e27376094f11490f74c65f3dc3a42c5"; + const char md5_modified_highlight[] = "66f3caef3a7d488a4fa1ad37fc06310e"; + const char md5_modified_square[] = "a456dad0bc6801ee2d6408a4394af563"; +#else + const char md5_original[] = "0e27376094f11490f74c65f3dc3a42c5"; + const char md5_modified_highlight[] = "66f3caef3a7d488a4fa1ad37fc06310e"; + const char md5_modified_square[] = "a456dad0bc6801ee2d6408a4394af563"; +#endif + + // Open a file with four annotations and load its first page. + ASSERT_TRUE(OpenDocument("annotation_highlight_square_with_ap.pdf")); + FPDF_PAGE page = LoadPage(0); + ASSERT_TRUE(page); + EXPECT_EQ(4, FPDFPage_GetAnnotCount(page)); + + // Check that the original file renders correctly. + { + std::unique_ptr bitmap = + RenderLoadedPageWithFlags(page, FPDF_ANNOT); + CompareBitmap(bitmap.get(), 612, 792, md5_original); + } + + FS_RECTF rect; + FS_RECTF new_rect; + + // Retrieve the highlight annotation which has its AP stream already defined. + { + std::unique_ptr annot( + FPDFPage_GetAnnot(page, 0)); + ASSERT_TRUE(annot); + EXPECT_EQ(FPDF_ANNOT_HIGHLIGHT, FPDFAnnot_GetSubtype(annot.get())); + + // Check that color cannot be set when an AP stream is defined already. + EXPECT_FALSE(FPDFAnnot_SetColor(annot.get(), FPDFANNOT_COLORTYPE_Color, 51, + 102, 153, 204)); + + // Verify its attachment points. + FS_QUADPOINTSF quadpoints; + ASSERT_TRUE(FPDFAnnot_GetAttachmentPoints(annot.get(), &quadpoints)); + EXPECT_NEAR(72.0000f, quadpoints.x1, 0.001f); + EXPECT_NEAR(720.792f, quadpoints.y1, 0.001f); + EXPECT_NEAR(132.055f, quadpoints.x4, 0.001f); + EXPECT_NEAR(704.796f, quadpoints.y4, 0.001f); + + // Check that updating the attachment points would succeed. + quadpoints.x1 -= 50.f; + quadpoints.x2 -= 50.f; + quadpoints.x3 -= 50.f; + quadpoints.x4 -= 50.f; + ASSERT_TRUE(FPDFAnnot_SetAttachmentPoints(annot.get(), &quadpoints)); + FS_QUADPOINTSF new_quadpoints; + ASSERT_TRUE(FPDFAnnot_GetAttachmentPoints(annot.get(), &new_quadpoints)); + EXPECT_EQ(quadpoints.x1, new_quadpoints.x1); + EXPECT_EQ(quadpoints.y1, new_quadpoints.y1); + EXPECT_EQ(quadpoints.x4, new_quadpoints.x4); + EXPECT_EQ(quadpoints.y4, new_quadpoints.y4); + + // Check that updating quadpoints does not change the annotation's position. + { + std::unique_ptr bitmap = + RenderLoadedPageWithFlags(page, FPDF_ANNOT); + CompareBitmap(bitmap.get(), 612, 792, md5_original); + } + + // Verify its annotation rectangle. + ASSERT_TRUE(FPDFAnnot_GetRect(annot.get(), &rect)); + EXPECT_NEAR(67.7299f, rect.left, 0.001f); + EXPECT_NEAR(704.296f, rect.bottom, 0.001f); + EXPECT_NEAR(136.325f, rect.right, 0.001f); + EXPECT_NEAR(721.292f, rect.top, 0.001f); + + // Check that updating the rectangle would succeed. + rect.left -= 60.f; + rect.right -= 60.f; + ASSERT_TRUE(FPDFAnnot_SetRect(annot.get(), &rect)); + ASSERT_TRUE(FPDFAnnot_GetRect(annot.get(), &new_rect)); + EXPECT_EQ(rect.right, new_rect.right); + } + + // Check that updating the rectangle changes the annotation's position. + { + std::unique_ptr bitmap = + RenderLoadedPageWithFlags(page, FPDF_ANNOT); + CompareBitmap(bitmap.get(), 612, 792, md5_modified_highlight); + } + + { + // Retrieve the square annotation which has its AP stream already defined. + std::unique_ptr annot( + FPDFPage_GetAnnot(page, 2)); + ASSERT_TRUE(annot); + EXPECT_EQ(FPDF_ANNOT_SQUARE, FPDFAnnot_GetSubtype(annot.get())); + + // Check that updating the rectangle would succeed. + ASSERT_TRUE(FPDFAnnot_GetRect(annot.get(), &rect)); + rect.left += 70.f; + rect.right += 70.f; + ASSERT_TRUE(FPDFAnnot_SetRect(annot.get(), &rect)); + ASSERT_TRUE(FPDFAnnot_GetRect(annot.get(), &new_rect)); + EXPECT_EQ(rect.right, new_rect.right); + + // Check that updating the rectangle changes the square annotation's + // position. + std::unique_ptr bitmap = + RenderLoadedPageWithFlags(page, FPDF_ANNOT); + CompareBitmap(bitmap.get(), 612, 792, md5_modified_square); + } + + UnloadPage(page); +} + +TEST_F(FPDFAnnotEmbeddertest, CountAttachmentPoints) { + // Open a file with multiline markup annotations. + ASSERT_TRUE(OpenDocument("annotation_markup_multiline_no_ap.pdf")); + FPDF_PAGE page = LoadPage(0); + ASSERT_TRUE(page); + { + std::unique_ptr annot( + FPDFPage_GetAnnot(page, 0)); + ASSERT_TRUE(annot); + + // This is a three line annotation. + EXPECT_EQ(3u, FPDFAnnot_CountAttachmentPoints(annot.get())); + } + UnloadPage(page); + + // null annotation should return 0 + EXPECT_EQ(0u, FPDFAnnot_CountAttachmentPoints(nullptr)); +} + +TEST_F(FPDFAnnotEmbeddertest, RemoveAnnotation) { + // Open a file with 3 annotations on its first page. + ASSERT_TRUE(OpenDocument("annotation_ink_multiple.pdf")); + // TODO(thestig): This test should use LoadPage() and UnloadPage(), but one of + // the FORM API calls in LoadPage() makes this test fail. So use + // FPDF_LoadPage() and FPDF_ClosePage() for now. + FPDF_PAGE page = FPDF_LoadPage(document(), 0); + ASSERT_TRUE(page); + EXPECT_EQ(3, FPDFPage_GetAnnotCount(page)); + + FS_RECTF rect; + + // Check that the annotations have the expected rectangle coordinates. + { + std::unique_ptr annot( + FPDFPage_GetAnnot(page, 0)); + ASSERT_TRUE(FPDFAnnot_GetRect(annot.get(), &rect)); + EXPECT_NEAR(86.1971f, rect.left, 0.001f); + } + + { + std::unique_ptr annot( + FPDFPage_GetAnnot(page, 1)); + ASSERT_TRUE(FPDFAnnot_GetRect(annot.get(), &rect)); + EXPECT_NEAR(149.8127f, rect.left, 0.001f); + } + + { + std::unique_ptr annot( + FPDFPage_GetAnnot(page, 2)); + ASSERT_TRUE(FPDFAnnot_GetRect(annot.get(), &rect)); + EXPECT_NEAR(351.8204f, rect.left, 0.001f); + } + + // Check that nothing happens when attempting to remove an annotation with an + // out-of-bound index. + EXPECT_FALSE(FPDFPage_RemoveAnnot(page, 4)); + EXPECT_FALSE(FPDFPage_RemoveAnnot(page, -1)); + EXPECT_EQ(3, FPDFPage_GetAnnotCount(page)); + + // Remove the second annotation. + EXPECT_TRUE(FPDFPage_RemoveAnnot(page, 1)); + EXPECT_EQ(2, FPDFPage_GetAnnotCount(page)); + EXPECT_FALSE(FPDFPage_GetAnnot(page, 2)); + + // Save the document, closing the page and document. + EXPECT_TRUE(FPDF_SaveAsCopy(document(), this, 0)); + FPDF_ClosePage(page); + + // TODO(npm): VerifySavedRendering changes annot rect dimensions by 1?? + // Open the saved document. + std::string new_file = GetString(); + FPDF_FILEACCESS file_access; + memset(&file_access, 0, sizeof(file_access)); + file_access.m_FileLen = new_file.size(); + file_access.m_GetBlock = GetBlockFromString; + file_access.m_Param = &new_file; + FPDF_DOCUMENT new_doc = FPDF_LoadCustomDocument(&file_access, nullptr); + ASSERT_TRUE(new_doc); + FPDF_PAGE new_page = FPDF_LoadPage(new_doc, 0); + ASSERT_TRUE(new_page); + + // Check that the saved document has 2 annotations on the first page. + EXPECT_EQ(2, FPDFPage_GetAnnotCount(new_page)); + + // Check that the remaining 2 annotations are the original 1st and 3rd ones + // by verifying their rectangle coordinates. + { + std::unique_ptr annot( + FPDFPage_GetAnnot(new_page, 0)); + ASSERT_TRUE(FPDFAnnot_GetRect(annot.get(), &rect)); + EXPECT_NEAR(86.1971f, rect.left, 0.001f); + } + + { + std::unique_ptr annot( + FPDFPage_GetAnnot(new_page, 1)); + ASSERT_TRUE(FPDFAnnot_GetRect(annot.get(), &rect)); + EXPECT_NEAR(351.8204f, rect.left, 0.001f); + } + FPDF_ClosePage(new_page); + FPDF_CloseDocument(new_doc); +} + +TEST_F(FPDFAnnotEmbeddertest, AddAndModifyPath) { +#if _FX_PLATFORM_ == _FX_PLATFORM_APPLE_ + const char md5_original[] = "c35408717759562d1f8bf33d317483d2"; + const char md5_modified_path[] = "873b92ea83ccf006e58415d866ce145b"; + const char md5_two_paths[] = "6f1f1c91f50240e9cc9d7c87c48b93a7"; + const char md5_new_annot[] = "078bf58f939645ac305854f31ee9a828"; +#else + const char md5_original[] = "964f89bbe8911e540a465cf1a64b7f7e"; + const char md5_modified_path[] = "5a4a6091cff648a4ece3ce7e245e3e38"; + const char md5_two_paths[] = "d6e4072a4415cfc6ec17201fb6be0ee0"; + const char md5_new_annot[] = "fc338b97bf66a656916c6198697a8a28"; +#endif + + // Open a file with two annotations and load its first page. + ASSERT_TRUE(OpenDocument("annotation_stamp_with_ap.pdf")); + FPDF_PAGE page = LoadPage(0); + ASSERT_TRUE(page); + EXPECT_EQ(2, FPDFPage_GetAnnotCount(page)); + + // Check that the page renders correctly. + { + std::unique_ptr bitmap = + RenderLoadedPageWithFlags(page, FPDF_ANNOT); + CompareBitmap(bitmap.get(), 595, 842, md5_original); + } + + { + // Retrieve the stamp annotation which has its AP stream already defined. + std::unique_ptr annot( + FPDFPage_GetAnnot(page, 0)); + ASSERT_TRUE(annot); + + // Check that this annotation has one path object and retrieve it. + EXPECT_EQ(1, FPDFAnnot_GetObjectCount(annot.get())); + ASSERT_EQ(32, FPDFPage_CountObjects(page)); + FPDF_PAGEOBJECT path = FPDFAnnot_GetObject(annot.get(), 1); + EXPECT_FALSE(path); + path = FPDFAnnot_GetObject(annot.get(), 0); + EXPECT_EQ(FPDF_PAGEOBJ_PATH, FPDFPageObj_GetType(path)); + EXPECT_TRUE(path); + + // Modify the color of the path object. + EXPECT_TRUE(FPDFPath_SetStrokeColor(path, 0, 0, 0, 255)); + EXPECT_TRUE(FPDFAnnot_UpdateObject(annot.get(), path)); + + // Check that the page with the modified annotation renders correctly. + { + std::unique_ptr bitmap = + RenderLoadedPageWithFlags(page, FPDF_ANNOT); + CompareBitmap(bitmap.get(), 595, 842, md5_modified_path); + } + + // Add a second path object to the same annotation. + FPDF_PAGEOBJECT dot = FPDFPageObj_CreateNewPath(7, 84); + EXPECT_TRUE(FPDFPath_BezierTo(dot, 9, 86, 10, 87, 11, 88)); + EXPECT_TRUE(FPDFPath_SetStrokeColor(dot, 255, 0, 0, 100)); + EXPECT_TRUE(FPDFPath_SetStrokeWidth(dot, 14)); + EXPECT_TRUE(FPDFPath_SetDrawMode(dot, 0, 1)); + EXPECT_TRUE(FPDFAnnot_AppendObject(annot.get(), dot)); + EXPECT_EQ(2, FPDFAnnot_GetObjectCount(annot.get())); + + // The object is in the annontation, not in the page, so the page object + // array should not change. + ASSERT_EQ(32, FPDFPage_CountObjects(page)); + + // Check that the page with an annotation with two paths renders correctly. + { + std::unique_ptr bitmap = + RenderLoadedPageWithFlags(page, FPDF_ANNOT); + CompareBitmap(bitmap.get(), 595, 842, md5_two_paths); + } + + // Delete the newly added path object. + EXPECT_TRUE(FPDFAnnot_RemoveObject(annot.get(), 1)); + EXPECT_EQ(1, FPDFAnnot_GetObjectCount(annot.get())); + ASSERT_EQ(32, FPDFPage_CountObjects(page)); + } + + // Check that the page renders the same as before. + { + std::unique_ptr bitmap = + RenderLoadedPageWithFlags(page, FPDF_ANNOT); + CompareBitmap(bitmap.get(), 595, 842, md5_modified_path); + } + + FS_RECTF rect; + + { + // Create another stamp annotation and set its annotation rectangle. + std::unique_ptr annot( + FPDFPage_CreateAnnot(page, FPDF_ANNOT_STAMP)); + ASSERT_TRUE(annot); + rect.left = 200.f; + rect.bottom = 400.f; + rect.right = 500.f; + rect.top = 600.f; + EXPECT_TRUE(FPDFAnnot_SetRect(annot.get(), &rect)); + + // Add a new path to the annotation. + FPDF_PAGEOBJECT check = FPDFPageObj_CreateNewPath(200, 500); + EXPECT_TRUE(FPDFPath_LineTo(check, 300, 400)); + EXPECT_TRUE(FPDFPath_LineTo(check, 500, 600)); + EXPECT_TRUE(FPDFPath_MoveTo(check, 350, 550)); + EXPECT_TRUE(FPDFPath_LineTo(check, 450, 450)); + EXPECT_TRUE(FPDFPath_SetStrokeColor(check, 0, 255, 255, 180)); + EXPECT_TRUE(FPDFPath_SetStrokeWidth(check, 8.35f)); + EXPECT_TRUE(FPDFPath_SetDrawMode(check, 0, 1)); + EXPECT_TRUE(FPDFAnnot_AppendObject(annot.get(), check)); + EXPECT_EQ(1, FPDFAnnot_GetObjectCount(annot.get())); + + // Check that the annotation's bounding box came from its rectangle. + FS_RECTF new_rect; + ASSERT_TRUE(FPDFAnnot_GetRect(annot.get(), &new_rect)); + EXPECT_EQ(rect.left, new_rect.left); + EXPECT_EQ(rect.bottom, new_rect.bottom); + EXPECT_EQ(rect.right, new_rect.right); + EXPECT_EQ(rect.top, new_rect.top); + } + + // Save the document, closing the page and document. + EXPECT_TRUE(FPDF_SaveAsCopy(document(), this, 0)); + UnloadPage(page); + + // Open the saved document. + OpenSavedDocument(); + page = LoadSavedPage(0); + VerifySavedRendering(page, 595, 842, md5_new_annot); + + // Check that the document has a correct count of annotations and objects. + EXPECT_EQ(3, FPDFPage_GetAnnotCount(page)); + + { + std::unique_ptr annot( + FPDFPage_GetAnnot(page, 2)); + ASSERT_TRUE(annot); + EXPECT_EQ(1, FPDFAnnot_GetObjectCount(annot.get())); + + // Check that the new annotation's rectangle is as defined. + FS_RECTF new_rect; + ASSERT_TRUE(FPDFAnnot_GetRect(annot.get(), &new_rect)); + EXPECT_EQ(rect.left, new_rect.left); + EXPECT_EQ(rect.bottom, new_rect.bottom); + EXPECT_EQ(rect.right, new_rect.right); + EXPECT_EQ(rect.top, new_rect.top); + } + + CloseSavedPage(page); + CloseSavedDocument(); +} + +TEST_F(FPDFAnnotEmbeddertest, ModifyAnnotationFlags) { + // Open a file with an annotation and load its first page. + ASSERT_TRUE(OpenDocument("annotation_highlight_rollover_ap.pdf")); + FPDF_PAGE page = LoadPage(0); + ASSERT_TRUE(page); + + // Check that the page renders correctly. + { + std::unique_ptr bitmap = + RenderLoadedPageWithFlags(page, FPDF_ANNOT); + CompareBitmap(bitmap.get(), 612, 792, "dc98f06da047bd8aabfa99562d2cbd1e"); + } + + { + // Retrieve the annotation. + std::unique_ptr annot( + FPDFPage_GetAnnot(page, 0)); + ASSERT_TRUE(annot); + + // Check that the original flag values are as expected. + int flags = FPDFAnnot_GetFlags(annot.get()); + EXPECT_FALSE(flags & FPDF_ANNOT_FLAG_HIDDEN); + EXPECT_TRUE(flags & FPDF_ANNOT_FLAG_PRINT); + + // Set the HIDDEN flag. + flags |= FPDF_ANNOT_FLAG_HIDDEN; + EXPECT_TRUE(FPDFAnnot_SetFlags(annot.get(), flags)); + flags = FPDFAnnot_GetFlags(annot.get()); + EXPECT_TRUE(flags & FPDF_ANNOT_FLAG_HIDDEN); + EXPECT_TRUE(flags & FPDF_ANNOT_FLAG_PRINT); + + // Check that the page renders correctly without rendering the annotation. + { + std::unique_ptr bitmap = + RenderLoadedPageWithFlags(page, FPDF_ANNOT); + CompareBitmap(bitmap.get(), 612, 792, "1940568c9ba33bac5d0b1ee9558c76b3"); + } + + // Unset the HIDDEN flag. + EXPECT_TRUE(FPDFAnnot_SetFlags(annot.get(), FPDF_ANNOT_FLAG_NONE)); + EXPECT_FALSE(FPDFAnnot_GetFlags(annot.get())); + flags &= ~FPDF_ANNOT_FLAG_HIDDEN; + EXPECT_TRUE(FPDFAnnot_SetFlags(annot.get(), flags)); + flags = FPDFAnnot_GetFlags(annot.get()); + EXPECT_FALSE(flags & FPDF_ANNOT_FLAG_HIDDEN); + EXPECT_TRUE(flags & FPDF_ANNOT_FLAG_PRINT); + + // Check that the page renders correctly as before. + { + std::unique_ptr bitmap = + RenderLoadedPageWithFlags(page, FPDF_ANNOT); + CompareBitmap(bitmap.get(), 612, 792, "dc98f06da047bd8aabfa99562d2cbd1e"); + } + } + + UnloadPage(page); +} + +TEST_F(FPDFAnnotEmbeddertest, AddAndModifyImage) { +#if _FX_PLATFORM_ == _FX_PLATFORM_APPLE_ + const char md5_original[] = "c35408717759562d1f8bf33d317483d2"; + const char md5_new_image[] = "ff012f5697436dfcaec25b32d1333596"; + const char md5_modified_image[] = "86cf8cb2755a7a2046a543e66d9c1e61"; +#else + const char md5_original[] = "964f89bbe8911e540a465cf1a64b7f7e"; + const char md5_new_image[] = "9ea8732dc9d579f68853f16892856208"; + const char md5_modified_image[] = "74239d2a8c55c9de1dbb9cd8781895aa"; +#endif + + // Open a file with two annotations and load its first page. + ASSERT_TRUE(OpenDocument("annotation_stamp_with_ap.pdf")); + FPDF_PAGE page = LoadPage(0); + ASSERT_TRUE(page); + EXPECT_EQ(2, FPDFPage_GetAnnotCount(page)); + + // Check that the page renders correctly. + { + std::unique_ptr bitmap = + RenderLoadedPageWithFlags(page, FPDF_ANNOT); + CompareBitmap(bitmap.get(), 595, 842, md5_original); + } + + constexpr int kBitmapSize = 200; + FPDF_BITMAP image_bitmap; + + { + // Create a stamp annotation and set its annotation rectangle. + std::unique_ptr annot( + FPDFPage_CreateAnnot(page, FPDF_ANNOT_STAMP)); + ASSERT_TRUE(annot); + FS_RECTF rect; + rect.left = 200.f; + rect.bottom = 600.f; + rect.right = 400.f; + rect.top = 800.f; + EXPECT_TRUE(FPDFAnnot_SetRect(annot.get(), &rect)); + + // Add a solid-color translucent image object to the new annotation. + image_bitmap = FPDFBitmap_Create(kBitmapSize, kBitmapSize, 1); + FPDFBitmap_FillRect(image_bitmap, 0, 0, kBitmapSize, kBitmapSize, + 0xeeeecccc); + EXPECT_EQ(kBitmapSize, FPDFBitmap_GetWidth(image_bitmap)); + EXPECT_EQ(kBitmapSize, FPDFBitmap_GetHeight(image_bitmap)); + FPDF_PAGEOBJECT image_object = FPDFPageObj_NewImageObj(document()); + ASSERT_TRUE(FPDFImageObj_SetBitmap(&page, 0, image_object, image_bitmap)); + ASSERT_TRUE(FPDFImageObj_SetMatrix(image_object, kBitmapSize, 0, 0, + kBitmapSize, 0, 0)); + FPDFPageObj_Transform(image_object, 1, 0, 0, 1, 200, 600); + EXPECT_TRUE(FPDFAnnot_AppendObject(annot.get(), image_object)); + } + + // Check that the page renders correctly with the new image object. + { + std::unique_ptr bitmap = + RenderLoadedPageWithFlags(page, FPDF_ANNOT); + CompareBitmap(bitmap.get(), 595, 842, md5_new_image); + } + + { + // Retrieve the newly added stamp annotation and its image object. + std::unique_ptr annot( + FPDFPage_GetAnnot(page, 2)); + ASSERT_TRUE(annot); + EXPECT_EQ(1, FPDFAnnot_GetObjectCount(annot.get())); + FPDF_PAGEOBJECT image_object = FPDFAnnot_GetObject(annot.get(), 0); + EXPECT_EQ(FPDF_PAGEOBJ_IMAGE, FPDFPageObj_GetType(image_object)); + + // Modify the image in the new annotation. + FPDFBitmap_FillRect(image_bitmap, 0, 0, kBitmapSize, kBitmapSize, + 0xff000000); + ASSERT_TRUE(FPDFImageObj_SetBitmap(&page, 0, image_object, image_bitmap)); + EXPECT_TRUE(FPDFAnnot_UpdateObject(annot.get(), image_object)); + } + + // Save the document, closing the page and document. + EXPECT_TRUE(FPDF_SaveAsCopy(document(), this, 0)); + UnloadPage(page); + FPDFBitmap_Destroy(image_bitmap); + + // Test that the saved document renders the modified image object correctly. + VerifySavedDocument(595, 842, md5_modified_image); +} + +TEST_F(FPDFAnnotEmbeddertest, AddAndModifyText) { +#if _FX_PLATFORM_ == _FX_PLATFORM_APPLE_ + const char md5_original[] = "c35408717759562d1f8bf33d317483d2"; + const char md5_new_text[] = "e5680ed048c2cfd9a1d27212cdf41286"; + const char md5_modified_text[] = "79f5cfb0b07caaf936f65f6a7a57ce77"; +#else + const char md5_original[] = "964f89bbe8911e540a465cf1a64b7f7e"; + const char md5_new_text[] = "00b14fa2dc1c90d1b0d034e1608efef5"; + const char md5_modified_text[] = "076c8f24a09ddc0e49f7e758edead6f0"; +#endif + + // Open a file with two annotations and load its first page. + ASSERT_TRUE(OpenDocument("annotation_stamp_with_ap.pdf")); + FPDF_PAGE page = LoadPage(0); + ASSERT_TRUE(page); + EXPECT_EQ(2, FPDFPage_GetAnnotCount(page)); + + // Check that the page renders correctly. + { + std::unique_ptr bitmap = + RenderLoadedPageWithFlags(page, FPDF_ANNOT); + CompareBitmap(bitmap.get(), 595, 842, md5_original); + } + + { + // Create a stamp annotation and set its annotation rectangle. + std::unique_ptr annot( + FPDFPage_CreateAnnot(page, FPDF_ANNOT_STAMP)); + ASSERT_TRUE(annot); + FS_RECTF rect; + rect.left = 200.f; + rect.bottom = 550.f; + rect.right = 450.f; + rect.top = 650.f; + EXPECT_TRUE(FPDFAnnot_SetRect(annot.get(), &rect)); + + // Add a translucent text object to the new annotation. + FPDF_PAGEOBJECT text_object = + FPDFPageObj_NewTextObj(document(), "Arial", 12.0f); + EXPECT_TRUE(text_object); + std::unique_ptr text = + GetFPDFWideString(L"I'm a translucent text laying on other text."); + EXPECT_TRUE(FPDFText_SetText(text_object, text.get())); + EXPECT_TRUE(FPDFText_SetFillColor(text_object, 0, 0, 255, 150)); + FPDFPageObj_Transform(text_object, 1, 0, 0, 1, 200, 600); + EXPECT_TRUE(FPDFAnnot_AppendObject(annot.get(), text_object)); + } + + // Check that the page renders correctly with the new text object. + { + std::unique_ptr bitmap = + RenderLoadedPageWithFlags(page, FPDF_ANNOT); + CompareBitmap(bitmap.get(), 595, 842, md5_new_text); + } + + { + // Retrieve the newly added stamp annotation and its text object. + std::unique_ptr annot( + FPDFPage_GetAnnot(page, 2)); + ASSERT_TRUE(annot); + EXPECT_EQ(1, FPDFAnnot_GetObjectCount(annot.get())); + FPDF_PAGEOBJECT text_object = FPDFAnnot_GetObject(annot.get(), 0); + EXPECT_EQ(FPDF_PAGEOBJ_TEXT, FPDFPageObj_GetType(text_object)); + + // Modify the text in the new annotation. + std::unique_ptr new_text = + GetFPDFWideString(L"New text!"); + EXPECT_TRUE(FPDFText_SetText(text_object, new_text.get())); + EXPECT_TRUE(FPDFAnnot_UpdateObject(annot.get(), text_object)); + } + + // Check that the page renders correctly with the modified text object. + { + std::unique_ptr bitmap = + RenderLoadedPageWithFlags(page, FPDF_ANNOT); + CompareBitmap(bitmap.get(), 595, 842, md5_modified_text); + } + + // Remove the new annotation, and check that the page renders as before. + EXPECT_TRUE(FPDFPage_RemoveAnnot(page, 2)); + { + std::unique_ptr bitmap = + RenderLoadedPageWithFlags(page, FPDF_ANNOT); + CompareBitmap(bitmap.get(), 595, 842, md5_original); + } + + UnloadPage(page); +} + +TEST_F(FPDFAnnotEmbeddertest, GetSetStringValue) { + // Open a file with four annotations and load its first page. + ASSERT_TRUE(OpenDocument("annotation_stamp_with_ap.pdf")); + FPDF_PAGE page = LoadPage(0); + ASSERT_TRUE(page); + + static constexpr char kDateKey[] = "M"; + static constexpr wchar_t kNewDate[] = L"D:201706282359Z00'00'"; + + { + // Retrieve the first annotation. + std::unique_ptr annot( + FPDFPage_GetAnnot(page, 0)); + ASSERT_TRUE(annot); + + // Check that a non-existent key does not exist. + EXPECT_FALSE(FPDFAnnot_HasKey(annot.get(), "none")); + + // Check that the string value of a non-string dictionary entry is empty. + static constexpr char kApKey[] = "AP"; + EXPECT_TRUE(FPDFAnnot_HasKey(annot.get(), kApKey)); + EXPECT_EQ(FPDF_OBJECT_REFERENCE, + FPDFAnnot_GetValueType(annot.get(), kApKey)); + EXPECT_EQ(2u, FPDFAnnot_GetStringValue(annot.get(), kApKey, nullptr, 0)); + + // Check that the string value of the hash is correct. + static constexpr char kHashKey[] = "AAPL:Hash"; + EXPECT_EQ(FPDF_OBJECT_NAME, FPDFAnnot_GetValueType(annot.get(), kHashKey)); + unsigned long len = + FPDFAnnot_GetStringValue(annot.get(), kHashKey, nullptr, 0); + std::vector buf(len); + EXPECT_EQ(66u, + FPDFAnnot_GetStringValue(annot.get(), kHashKey, buf.data(), len)); + EXPECT_STREQ(L"395fbcb98d558681742f30683a62a2ad", + BufferToWString(buf).c_str()); + + // Check that the string value of the modified date is correct. + EXPECT_EQ(FPDF_OBJECT_NAME, FPDFAnnot_GetValueType(annot.get(), kHashKey)); + len = FPDFAnnot_GetStringValue(annot.get(), kDateKey, nullptr, 0); + buf.clear(); + buf.resize(len); + EXPECT_EQ(44u, + FPDFAnnot_GetStringValue(annot.get(), kDateKey, buf.data(), len)); + EXPECT_STREQ(L"D:201706071721Z00'00'", BufferToWString(buf).c_str()); + + // Update the date entry for the annotation. + std::unique_ptr text = + GetFPDFWideString(kNewDate); + EXPECT_TRUE(FPDFAnnot_SetStringValue(annot.get(), kDateKey, text.get())); + } + + // Save the document, closing the page and document. + EXPECT_TRUE(FPDF_SaveAsCopy(document(), this, 0)); + UnloadPage(page); + +#if _FX_PLATFORM_ == _FX_PLATFORM_APPLE_ + const char md5[] = "4d64e61c9c0f8c60ab3cc3234bb73b1c"; +#else + const char md5[] = "c96ee1f316d7f5a1b154de9f9d467f01"; +#endif + + // Open the saved annotation. + OpenSavedDocument(); + page = LoadSavedPage(0); + VerifySavedRendering(page, 595, 842, md5); + { + std::unique_ptr new_annot( + FPDFPage_GetAnnot(page, 0)); + + // Check that the string value of the modified date is the newly-set value. + EXPECT_EQ(FPDF_OBJECT_STRING, + FPDFAnnot_GetValueType(new_annot.get(), kDateKey)); + unsigned long len = + FPDFAnnot_GetStringValue(new_annot.get(), kDateKey, nullptr, 0); + std::vector buf(len); + EXPECT_EQ(44u, FPDFAnnot_GetStringValue(new_annot.get(), kDateKey, + buf.data(), len)); + EXPECT_STREQ(kNewDate, BufferToWString(buf).c_str()); + } + + CloseSavedPage(page); + CloseSavedDocument(); +} + +TEST_F(FPDFAnnotEmbeddertest, GetSetAP) { + // Open a file with four annotations and load its first page. + ASSERT_TRUE(OpenDocument("annotation_stamp_with_ap.pdf")); + FPDF_PAGE page = LoadPage(0); + ASSERT_TRUE(page); + + { + // Retrieve the first annotation. + std::unique_ptr annot( + FPDFPage_GetAnnot(page, 0)); + ASSERT_TRUE(annot); + + // Check that the string value of an AP returns the expected length. + unsigned long normal_len = FPDFAnnot_GetAP( + annot.get(), FPDF_ANNOT_APPEARANCEMODE_NORMAL, nullptr, 0); + EXPECT_EQ(73970u, normal_len); + + // Check that the string value of an AP is not returned if the buffer is too + // small. The result buffer should be overwritten with an empty string. + std::vector buf(normal_len - 1); + // Write L"z" in the buffer to verify it's not overwritten. + wcscpy(reinterpret_cast(buf.data()), L"z"); + EXPECT_EQ(73970u, + FPDFAnnot_GetAP(annot.get(), FPDF_ANNOT_APPEARANCEMODE_NORMAL, + buf.data(), buf.size())); + std::string ap = BufferToString(buf); + EXPECT_STREQ("z", ap.c_str()); + + // Check that the string value of an AP is returned through a buffer that is + // the right size. + buf.clear(); + buf.resize(normal_len); + EXPECT_EQ(73970u, + FPDFAnnot_GetAP(annot.get(), FPDF_ANNOT_APPEARANCEMODE_NORMAL, + buf.data(), buf.size())); + ap = BufferToString(buf); + EXPECT_THAT(ap, testing::StartsWith("q Q q 7.442786 w 2 J")); + EXPECT_THAT(ap, testing::EndsWith("c 716.5381 327.7156 l S Q Q")); + + // Check that the string value of an AP is returned through a buffer that is + // larger than necessary. + buf.clear(); + buf.resize(normal_len + 1); + EXPECT_EQ(73970u, + FPDFAnnot_GetAP(annot.get(), FPDF_ANNOT_APPEARANCEMODE_NORMAL, + buf.data(), buf.size())); + ap = BufferToString(buf); + EXPECT_THAT(ap, testing::StartsWith("q Q q 7.442786 w 2 J")); + EXPECT_THAT(ap, testing::EndsWith("c 716.5381 327.7156 l S Q Q")); + + // Check that getting an AP for a mode that does not have an AP returns an + // empty string. + unsigned long rollover_len = FPDFAnnot_GetAP( + annot.get(), FPDF_ANNOT_APPEARANCEMODE_ROLLOVER, nullptr, 0); + EXPECT_EQ(2u, rollover_len); + + buf.clear(); + buf.resize(1000); + EXPECT_EQ(2u, + FPDFAnnot_GetAP(annot.get(), FPDF_ANNOT_APPEARANCEMODE_ROLLOVER, + buf.data(), buf.size())); + EXPECT_STREQ("", BufferToString(buf).c_str()); + + // Check that setting the AP for an invalid appearance mode fails. + std::unique_ptr apText = + GetFPDFWideString(L"new test ap"); + EXPECT_FALSE(FPDFAnnot_SetAP(annot.get(), -1, apText.get())); + EXPECT_FALSE(FPDFAnnot_SetAP(annot.get(), FPDF_ANNOT_APPEARANCEMODE_COUNT, + apText.get())); + EXPECT_FALSE(FPDFAnnot_SetAP( + annot.get(), FPDF_ANNOT_APPEARANCEMODE_COUNT + 1, apText.get())); + + // Set the AP correctly now. + EXPECT_TRUE(FPDFAnnot_SetAP(annot.get(), FPDF_ANNOT_APPEARANCEMODE_ROLLOVER, + apText.get())); + + // Check that the new annotation value is equal to the value we just set. + rollover_len = FPDFAnnot_GetAP( + annot.get(), FPDF_ANNOT_APPEARANCEMODE_ROLLOVER, nullptr, 0); + EXPECT_EQ(24u, rollover_len); + buf.clear(); + buf.resize(rollover_len); + EXPECT_EQ(24u, + FPDFAnnot_GetAP(annot.get(), FPDF_ANNOT_APPEARANCEMODE_ROLLOVER, + buf.data(), buf.size())); + EXPECT_STREQ(L"new test ap", BufferToWString(buf).c_str()); + + // Check that the Normal AP was not touched when the Rollover AP was set. + buf.clear(); + buf.resize(normal_len); + EXPECT_EQ(73970u, + FPDFAnnot_GetAP(annot.get(), FPDF_ANNOT_APPEARANCEMODE_NORMAL, + buf.data(), buf.size())); + ap = BufferToString(buf); + EXPECT_THAT(ap, testing::StartsWith("q Q q 7.442786 w 2 J")); + EXPECT_THAT(ap, testing::EndsWith("c 716.5381 327.7156 l S Q Q")); + } + + // Save the modified document, then reopen it. + EXPECT_TRUE(FPDF_SaveAsCopy(document(), this, 0)); + UnloadPage(page); + + OpenSavedDocument(); + page = LoadSavedPage(0); + { + std::unique_ptr new_annot( + FPDFPage_GetAnnot(page, 0)); + + // Check that the new annotation value is equal to the value we set before + // saving. + unsigned long rollover_len = FPDFAnnot_GetAP( + new_annot.get(), FPDF_ANNOT_APPEARANCEMODE_ROLLOVER, nullptr, 0); + EXPECT_EQ(24u, rollover_len); + std::vector buf(rollover_len); + EXPECT_EQ(24u, FPDFAnnot_GetAP(new_annot.get(), + FPDF_ANNOT_APPEARANCEMODE_ROLLOVER, + buf.data(), buf.size())); + EXPECT_STREQ(L"new test ap", BufferToWString(buf).c_str()); + } + + // Close saved document. + CloseSavedPage(page); + CloseSavedDocument(); +} + +TEST_F(FPDFAnnotEmbeddertest, RemoveOptionalAP) { + // Open a file with four annotations and load its first page. + ASSERT_TRUE(OpenDocument("annotation_stamp_with_ap.pdf")); + FPDF_PAGE page = LoadPage(0); + ASSERT_TRUE(page); + + { + // Retrieve the first annotation. + std::unique_ptr annot( + FPDFPage_GetAnnot(page, 0)); + ASSERT_TRUE(annot); + + // Set Down AP. Normal AP is already set. + std::unique_ptr apText = + GetFPDFWideString(L"new test ap"); + EXPECT_TRUE(FPDFAnnot_SetAP(annot.get(), FPDF_ANNOT_APPEARANCEMODE_DOWN, + apText.get())); + EXPECT_EQ(73970u, + FPDFAnnot_GetAP(annot.get(), FPDF_ANNOT_APPEARANCEMODE_NORMAL, + nullptr, 0)); + EXPECT_EQ(24u, FPDFAnnot_GetAP(annot.get(), FPDF_ANNOT_APPEARANCEMODE_DOWN, + nullptr, 0)); + + // Check that setting the Down AP to null removes the Down entry but keeps + // Normal intact. + EXPECT_TRUE( + FPDFAnnot_SetAP(annot.get(), FPDF_ANNOT_APPEARANCEMODE_DOWN, nullptr)); + EXPECT_EQ(73970u, + FPDFAnnot_GetAP(annot.get(), FPDF_ANNOT_APPEARANCEMODE_NORMAL, + nullptr, 0)); + EXPECT_EQ(2u, FPDFAnnot_GetAP(annot.get(), FPDF_ANNOT_APPEARANCEMODE_DOWN, + nullptr, 0)); + } + + UnloadPage(page); +} + +TEST_F(FPDFAnnotEmbeddertest, RemoveRequiredAP) { + // Open a file with four annotations and load its first page. + ASSERT_TRUE(OpenDocument("annotation_stamp_with_ap.pdf")); + FPDF_PAGE page = LoadPage(0); + ASSERT_TRUE(page); + + { + // Retrieve the first annotation. + std::unique_ptr annot( + FPDFPage_GetAnnot(page, 0)); + ASSERT_TRUE(annot); + + // Set Down AP. Normal AP is already set. + std::unique_ptr apText = + GetFPDFWideString(L"new test ap"); + EXPECT_TRUE(FPDFAnnot_SetAP(annot.get(), FPDF_ANNOT_APPEARANCEMODE_DOWN, + apText.get())); + EXPECT_EQ(73970u, + FPDFAnnot_GetAP(annot.get(), FPDF_ANNOT_APPEARANCEMODE_NORMAL, + nullptr, 0)); + EXPECT_EQ(24u, FPDFAnnot_GetAP(annot.get(), FPDF_ANNOT_APPEARANCEMODE_DOWN, + nullptr, 0)); + + // Check that setting the Normal AP to null removes the whole AP dictionary. + EXPECT_TRUE(FPDFAnnot_SetAP(annot.get(), FPDF_ANNOT_APPEARANCEMODE_NORMAL, + nullptr)); + EXPECT_EQ(2u, FPDFAnnot_GetAP(annot.get(), FPDF_ANNOT_APPEARANCEMODE_NORMAL, + nullptr, 0)); + EXPECT_EQ(2u, FPDFAnnot_GetAP(annot.get(), FPDF_ANNOT_APPEARANCEMODE_DOWN, + nullptr, 0)); + } + + UnloadPage(page); +} + +TEST_F(FPDFAnnotEmbeddertest, ExtractLinkedAnnotations) { + // Open a file with annotations and load its first page. + ASSERT_TRUE(OpenDocument("annotation_highlight_square_with_ap.pdf")); + FPDF_PAGE page = LoadPage(0); + ASSERT_TRUE(page); + EXPECT_EQ(-1, FPDFPage_GetAnnotIndex(page, nullptr)); + + { + // Retrieve the highlight annotation which has its popup defined. + std::unique_ptr annot( + FPDFPage_GetAnnot(page, 0)); + ASSERT_TRUE(annot); + EXPECT_EQ(FPDF_ANNOT_HIGHLIGHT, FPDFAnnot_GetSubtype(annot.get())); + EXPECT_EQ(0, FPDFPage_GetAnnotIndex(page, annot.get())); + static constexpr char kPopupKey[] = "Popup"; + ASSERT_TRUE(FPDFAnnot_HasKey(annot.get(), kPopupKey)); + ASSERT_EQ(FPDF_OBJECT_REFERENCE, + FPDFAnnot_GetValueType(annot.get(), kPopupKey)); + + // Retrieve and verify the popup of the highlight annotation. + std::unique_ptr popup( + FPDFAnnot_GetLinkedAnnot(annot.get(), kPopupKey)); + ASSERT_TRUE(popup); + EXPECT_EQ(FPDF_ANNOT_POPUP, FPDFAnnot_GetSubtype(popup.get())); + EXPECT_EQ(1, FPDFPage_GetAnnotIndex(page, popup.get())); + FS_RECTF rect; + ASSERT_TRUE(FPDFAnnot_GetRect(popup.get(), &rect)); + EXPECT_NEAR(612.0f, rect.left, 0.001f); + EXPECT_NEAR(578.792, rect.bottom, 0.001f); + + // Attempting to retrieve |annot|'s "IRT"-linked annotation would fail, + // since "IRT" is not a key in |annot|'s dictionary. + static constexpr char kIRTKey[] = "IRT"; + ASSERT_FALSE(FPDFAnnot_HasKey(annot.get(), kIRTKey)); + EXPECT_FALSE(FPDFAnnot_GetLinkedAnnot(annot.get(), kIRTKey)); + + // Attempting to retrieve |annot|'s parent dictionary as an annotation + // would fail, since its parent is not an annotation. + static constexpr char kPKey[] = "P"; + ASSERT_TRUE(FPDFAnnot_HasKey(annot.get(), kPKey)); + EXPECT_EQ(FPDF_OBJECT_REFERENCE, + FPDFAnnot_GetValueType(annot.get(), kPKey)); + EXPECT_FALSE(FPDFAnnot_GetLinkedAnnot(annot.get(), kPKey)); + } + + UnloadPage(page); +} + +TEST_F(FPDFAnnotEmbeddertest, GetFormFieldFlagsTextField) { + // Open file with form text fields. + ASSERT_TRUE(OpenDocument("text_form_multiple.pdf")); + FPDF_PAGE page = LoadPage(0); + ASSERT_TRUE(page); + + { + // Retrieve the first annotation: user-editable text field. + std::unique_ptr annot( + FPDFPage_GetAnnot(page, 0)); + ASSERT_TRUE(annot); + + // Check that the flag values are as expected. + int flags = FPDFAnnot_GetFormFieldFlags(page, annot.get()); + EXPECT_FALSE(flags & FPDF_FORMFLAG_READONLY); + } + + { + // Retrieve the second annotation: read-only text field. + std::unique_ptr annot( + FPDFPage_GetAnnot(page, 1)); + ASSERT_TRUE(annot); + + // Check that the flag values are as expected. + int flags = FPDFAnnot_GetFormFieldFlags(page, annot.get()); + EXPECT_TRUE(flags & FPDF_FORMFLAG_READONLY); + } + + UnloadPage(page); +} + +TEST_F(FPDFAnnotEmbeddertest, GetFormFieldFlagsComboBox) { + // Open file with form text fields. + ASSERT_TRUE(OpenDocument("combobox_form.pdf")); + FPDF_PAGE page = LoadPage(0); + ASSERT_TRUE(page); + + { + // Retrieve the first annotation: user-editable combobox. + std::unique_ptr annot( + FPDFPage_GetAnnot(page, 0)); + ASSERT_TRUE(annot); + + // Check that the flag values are as expected. + int flags = FPDFAnnot_GetFormFieldFlags(page, annot.get()); + EXPECT_FALSE(flags & FPDF_FORMFLAG_READONLY); + EXPECT_TRUE(flags & FPDF_FORMFLAG_CHOICE_COMBO); + EXPECT_TRUE(flags & FPDF_FORMFLAG_CHOICE_EDIT); + } + + { + // Retrieve the second annotation: regular combobox. + std::unique_ptr annot( + FPDFPage_GetAnnot(page, 1)); + ASSERT_TRUE(annot); + + // Check that the flag values are as expected. + int flags = FPDFAnnot_GetFormFieldFlags(page, annot.get()); + EXPECT_FALSE(flags & FPDF_FORMFLAG_READONLY); + EXPECT_TRUE(flags & FPDF_FORMFLAG_CHOICE_COMBO); + EXPECT_FALSE(flags & FPDF_FORMFLAG_CHOICE_EDIT); + } + + { + // Retrieve the third annotation: read-only combobox. + std::unique_ptr annot( + FPDFPage_GetAnnot(page, 2)); + ASSERT_TRUE(annot); + + // Check that the flag values are as expected. + int flags = FPDFAnnot_GetFormFieldFlags(page, annot.get()); + EXPECT_TRUE(flags & FPDF_FORMFLAG_READONLY); + EXPECT_TRUE(flags & FPDF_FORMFLAG_CHOICE_COMBO); + EXPECT_FALSE(flags & FPDF_FORMFLAG_CHOICE_EDIT); + } + + UnloadPage(page); +} + +TEST_F(FPDFAnnotEmbeddertest, GetFormAnnotNull) { + // Open file with form text fields. + EXPECT_TRUE(OpenDocument("text_form.pdf")); + FPDF_PAGE page = LoadPage(0); + ASSERT_TRUE(page); + + // Attempt to get an annotation where no annotation exists on page. + FPDF_ANNOTATION annot = + FPDFAnnot_GetFormFieldAtPoint(form_handle(), page, 0, 0); + EXPECT_FALSE(annot); + + UnloadPage(page); +} + +TEST_F(FPDFAnnotEmbeddertest, GetFormAnnotAndCheckFlagsTextField) { + // Open file with form text fields. + EXPECT_TRUE(OpenDocument("text_form_multiple.pdf")); + FPDF_PAGE page = LoadPage(0); + ASSERT_TRUE(page); + + { + // Retrieve user-editable text field annotation. + std::unique_ptr annot( + FPDFAnnot_GetFormFieldAtPoint(form_handle(), page, 105, 118)); + ASSERT_TRUE(annot); + + // Check that interactive form annotation flag values are as expected. + int flags = FPDFAnnot_GetFormFieldFlags(page, annot.get()); + EXPECT_FALSE(flags & FPDF_FORMFLAG_READONLY); + } + + { + // Retrieve read-only text field annotation. + std::unique_ptr annot( + FPDFAnnot_GetFormFieldAtPoint(form_handle(), page, 105, 202)); + ASSERT_TRUE(annot); + + // Check that interactive form annotation flag values are as expected. + int flags = FPDFAnnot_GetFormFieldFlags(page, annot.get()); + EXPECT_TRUE(flags & FPDF_FORMFLAG_READONLY); + } + + UnloadPage(page); +} + +TEST_F(FPDFAnnotEmbeddertest, GetFormAnnotAndCheckFlagsComboBox) { + // Open file with form comboboxes. + EXPECT_TRUE(OpenDocument("combobox_form.pdf")); + FPDF_PAGE page = LoadPage(0); + ASSERT_TRUE(page); + + { + // Retrieve user-editable combobox annotation. + std::unique_ptr annot( + FPDFAnnot_GetFormFieldAtPoint(form_handle(), page, 102, 363)); + ASSERT_TRUE(annot); + + // Check that interactive form annotation flag values are as expected. + int flags = FPDFAnnot_GetFormFieldFlags(page, annot.get()); + EXPECT_FALSE(flags & FPDF_FORMFLAG_READONLY); + EXPECT_TRUE(flags & FPDF_FORMFLAG_CHOICE_COMBO); + EXPECT_TRUE(flags & FPDF_FORMFLAG_CHOICE_EDIT); + } + + { + // Retrieve regular combobox annotation. + std::unique_ptr annot( + FPDFAnnot_GetFormFieldAtPoint(form_handle(), page, 102, 413)); + ASSERT_TRUE(annot); + + // Check that interactive form annotation flag values are as expected. + int flags = FPDFAnnot_GetFormFieldFlags(page, annot.get()); + EXPECT_FALSE(flags & FPDF_FORMFLAG_READONLY); + EXPECT_TRUE(flags & FPDF_FORMFLAG_CHOICE_COMBO); + EXPECT_FALSE(flags & FPDF_FORMFLAG_CHOICE_EDIT); + } + + { + // Retrieve read-only combobox annotation. + std::unique_ptr annot( + FPDFAnnot_GetFormFieldAtPoint(form_handle(), page, 102, 513)); + ASSERT_TRUE(annot); + + // Check that interactive form annotation flag values are as expected. + int flags = FPDFAnnot_GetFormFieldFlags(page, annot.get()); + EXPECT_TRUE(flags & FPDF_FORMFLAG_READONLY); + EXPECT_TRUE(flags & FPDF_FORMFLAG_CHOICE_COMBO); + EXPECT_FALSE(flags & FPDF_FORMFLAG_CHOICE_EDIT); + } + + UnloadPage(page); +} diff --git a/fpdfsdk/fpdf_attachment.cpp b/fpdfsdk/fpdf_attachment.cpp new file mode 100644 index 0000000000..0bb9364834 --- /dev/null +++ b/fpdfsdk/fpdf_attachment.cpp @@ -0,0 +1,275 @@ +// Copyright 2017 PDFium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "public/fpdf_attachment.h" + +#include +#include + +#include "core/fdrm/crypto/fx_crypt.h" +#include "core/fpdfapi/parser/cpdf_array.h" +#include "core/fpdfapi/parser/cpdf_document.h" +#include "core/fpdfapi/parser/cpdf_name.h" +#include "core/fpdfapi/parser/cpdf_number.h" +#include "core/fpdfapi/parser/cpdf_reference.h" +#include "core/fpdfapi/parser/cpdf_string.h" +#include "core/fpdfapi/parser/fpdf_parser_decode.h" +#include "core/fpdfdoc/cpdf_filespec.h" +#include "core/fpdfdoc/cpdf_nametree.h" +#include "core/fxcrt/cfx_datetime.h" +#include "core/fxcrt/fx_extension.h" +#include "fpdfsdk/cpdfsdk_helpers.h" + +namespace { + +constexpr char kChecksumKey[] = "CheckSum"; + +CPDF_Object* CPDFObjectFromFPDFAttachment(FPDF_ATTACHMENT attachment) { + return static_cast(attachment); +} + +ByteString CFXByteStringHexDecode(const ByteString& bsHex) { + uint8_t* result = nullptr; + uint32_t size = 0; + HexDecode(bsHex.raw_str(), bsHex.GetLength(), &result, &size); + ByteString bsDecoded(result, size); + FX_Free(result); + return bsDecoded; +} + +ByteString GenerateMD5Base16(const void* contents, const unsigned long len) { + uint8_t digest[16]; + CRYPT_MD5Generate(reinterpret_cast(contents), len, digest); + char buf[32]; + for (int i = 0; i < 16; ++i) + FXSYS_IntToTwoHexChars(digest[i], &buf[i * 2]); + + return ByteString(buf, 32); +} + +} // namespace + +FPDF_EXPORT int FPDF_CALLCONV +FPDFDoc_GetAttachmentCount(FPDF_DOCUMENT document) { + CPDF_Document* pDoc = CPDFDocumentFromFPDFDocument(document); + if (!pDoc) + return 0; + + return CPDF_NameTree(pDoc, "EmbeddedFiles").GetCount(); +} + +FPDF_EXPORT FPDF_ATTACHMENT FPDF_CALLCONV +FPDFDoc_AddAttachment(FPDF_DOCUMENT document, FPDF_WIDESTRING name) { + CPDF_Document* pDoc = CPDFDocumentFromFPDFDocument(document); + WideString wsName = + WideString::FromUTF16LE(name, WideString::WStringLength(name)); + if (!pDoc || wsName.IsEmpty()) + return nullptr; + + CPDF_Dictionary* pRoot = pDoc->GetRoot(); + if (!pRoot) + return nullptr; + + // Retrieve the document's Names dictionary; create it if missing. + CPDF_Dictionary* pNames = pRoot->GetDictFor("Names"); + if (!pNames) { + pNames = pDoc->NewIndirect(); + pRoot->SetNewFor("Names", pDoc, pNames->GetObjNum()); + } + + // Create the EmbeddedFiles dictionary if missing. + if (!pNames->GetDictFor("EmbeddedFiles")) { + CPDF_Dictionary* pFiles = pDoc->NewIndirect(); + pFiles->SetNewFor("Names"); + pNames->SetNewFor("EmbeddedFiles", pDoc, + pFiles->GetObjNum()); + } + + // Set up the basic entries in the filespec dictionary. + CPDF_Dictionary* pFile = pDoc->NewIndirect(); + pFile->SetNewFor("Type", "Filespec"); + pFile->SetNewFor("UF", wsName); + pFile->SetNewFor("F", wsName); + + // Add the new attachment name and filespec into the document's EmbeddedFiles. + CPDF_NameTree nameTree(pDoc, "EmbeddedFiles"); + if (!nameTree.AddValueAndName( + pdfium::MakeUnique(pDoc, pFile->GetObjNum()), + wsName)) { + return nullptr; + } + + return pFile; +} + +FPDF_EXPORT FPDF_ATTACHMENT FPDF_CALLCONV +FPDFDoc_GetAttachment(FPDF_DOCUMENT document, int index) { + CPDF_Document* pDoc = CPDFDocumentFromFPDFDocument(document); + if (!pDoc || index < 0) + return nullptr; + + CPDF_NameTree nameTree(pDoc, "EmbeddedFiles"); + if (static_cast(index) >= nameTree.GetCount()) + return nullptr; + + WideString csName; + return nameTree.LookupValueAndName(index, &csName); +} + +FPDF_EXPORT FPDF_BOOL FPDF_CALLCONV +FPDFDoc_DeleteAttachment(FPDF_DOCUMENT document, int index) { + CPDF_Document* pDoc = CPDFDocumentFromFPDFDocument(document); + if (!pDoc || index < 0) + return false; + + CPDF_NameTree nameTree(pDoc, "EmbeddedFiles"); + if (static_cast(index) >= nameTree.GetCount()) + return false; + + return nameTree.DeleteValueAndName(index); +} + +FPDF_EXPORT unsigned long FPDF_CALLCONV +FPDFAttachment_GetName(FPDF_ATTACHMENT attachment, + void* buffer, + unsigned long buflen) { + CPDF_Object* pFile = CPDFObjectFromFPDFAttachment(attachment); + if (!pFile) + return 0; + + return Utf16EncodeMaybeCopyAndReturnLength(CPDF_FileSpec(pFile).GetFileName(), + buffer, buflen); +} + +FPDF_EXPORT FPDF_BOOL FPDF_CALLCONV +FPDFAttachment_HasKey(FPDF_ATTACHMENT attachment, FPDF_BYTESTRING key) { + CPDF_Object* pFile = CPDFObjectFromFPDFAttachment(attachment); + if (!pFile) + return 0; + + CPDF_Dictionary* pParamsDict = CPDF_FileSpec(pFile).GetParamsDict(); + return pParamsDict ? pParamsDict->KeyExist(key) : 0; +} + +FPDF_EXPORT FPDF_OBJECT_TYPE FPDF_CALLCONV +FPDFAttachment_GetValueType(FPDF_ATTACHMENT attachment, FPDF_BYTESTRING key) { + if (!FPDFAttachment_HasKey(attachment, key)) + return FPDF_OBJECT_UNKNOWN; + + CPDF_FileSpec spec(CPDFObjectFromFPDFAttachment(attachment)); + CPDF_Object* pObj = spec.GetParamsDict()->GetObjectFor(key); + return pObj ? pObj->GetType() : FPDF_OBJECT_UNKNOWN; +} + +FPDF_EXPORT FPDF_BOOL FPDF_CALLCONV +FPDFAttachment_SetStringValue(FPDF_ATTACHMENT attachment, + FPDF_BYTESTRING key, + FPDF_WIDESTRING value) { + CPDF_Object* pFile = CPDFObjectFromFPDFAttachment(attachment); + if (!pFile) + return false; + + CPDF_Dictionary* pParamsDict = CPDF_FileSpec(pFile).GetParamsDict(); + if (!pParamsDict) + return false; + + ByteString bsKey = key; + ByteString bsValue = CFXByteStringFromFPDFWideString(value); + bool bEncodedAsHex = bsKey == kChecksumKey; + if (bEncodedAsHex) + bsValue = CFXByteStringHexDecode(bsValue); + + pParamsDict->SetNewFor(bsKey, bsValue, bEncodedAsHex); + return true; +} + +FPDF_EXPORT unsigned long FPDF_CALLCONV +FPDFAttachment_GetStringValue(FPDF_ATTACHMENT attachment, + FPDF_BYTESTRING key, + void* buffer, + unsigned long buflen) { + CPDF_Object* pFile = CPDFObjectFromFPDFAttachment(attachment); + if (!pFile) + return 0; + + CPDF_Dictionary* pParamsDict = CPDF_FileSpec(pFile).GetParamsDict(); + if (!pParamsDict) + return 0; + + ByteString bsKey = key; + WideString value = pParamsDict->GetUnicodeTextFor(bsKey); + if (bsKey == kChecksumKey && !value.IsEmpty()) { + CPDF_String* stringValue = pParamsDict->GetObjectFor(bsKey)->AsString(); + if (stringValue->IsHex()) { + ByteString encoded = PDF_EncodeString(stringValue->GetString(), true); + value = CPDF_String(nullptr, encoded, false).GetUnicodeText(); + } + } + + return Utf16EncodeMaybeCopyAndReturnLength(value, buffer, buflen); +} + +FPDF_EXPORT FPDF_BOOL FPDF_CALLCONV +FPDFAttachment_SetFile(FPDF_ATTACHMENT attachment, + FPDF_DOCUMENT document, + const void* contents, + const unsigned long len) { + CPDF_Object* pFile = CPDFObjectFromFPDFAttachment(attachment); + CPDF_Document* pDoc = CPDFDocumentFromFPDFDocument(document); + if (!pFile || !pFile->IsDictionary() || !pDoc || len > INT_MAX) + return false; + + // An empty content must have a zero length. + if (!contents && len != 0) + return false; + + // Create a dictionary for the new embedded file stream. + auto pFileStreamDict = pdfium::MakeUnique(); + CPDF_Dictionary* pParamsDict = + pFileStreamDict->SetNewFor("Params"); + + // Set the size of the new file in the dictionary. + pFileStreamDict->SetNewFor("DL", static_cast(len)); + pParamsDict->SetNewFor("Size", static_cast(len)); + + // Set the creation date of the new attachment in the dictionary. + CFX_DateTime dateTime = CFX_DateTime::Now(); + pParamsDict->SetNewFor( + "CreationDate", + ByteString::Format("D:%d%02d%02d%02d%02d%02d", dateTime.GetYear(), + dateTime.GetMonth(), dateTime.GetDay(), + dateTime.GetHour(), dateTime.GetMinute(), + dateTime.GetSecond()), + false); + + // Set the checksum of the new attachment in the dictionary. + pParamsDict->SetNewFor( + kChecksumKey, CFXByteStringHexDecode(GenerateMD5Base16(contents, len)), + true); + + // Create the file stream and have the filespec dictionary link to it. + std::unique_ptr stream(FX_Alloc(uint8_t, len)); + memcpy(stream.get(), contents, len); + CPDF_Stream* pFileStream = pDoc->NewIndirect( + std::move(stream), len, std::move(pFileStreamDict)); + CPDF_Dictionary* pEFDict = + pFile->AsDictionary()->SetNewFor("EF"); + pEFDict->SetNewFor("F", pDoc, pFileStream->GetObjNum()); + return true; +} + +FPDF_EXPORT unsigned long FPDF_CALLCONV +FPDFAttachment_GetFile(FPDF_ATTACHMENT attachment, + void* buffer, + unsigned long buflen) { + CPDF_Object* pFile = CPDFObjectFromFPDFAttachment(attachment); + if (!pFile) + return 0; + + CPDF_Stream* pFileStream = CPDF_FileSpec(pFile).GetFileStream(); + if (!pFileStream) + return 0; + + return DecodeStreamMaybeCopyAndReturnLength(pFileStream, buffer, buflen); +} diff --git a/fpdfsdk/fpdf_attachment_embeddertest.cpp b/fpdfsdk/fpdf_attachment_embeddertest.cpp new file mode 100644 index 0000000000..dd9b5aebf3 --- /dev/null +++ b/fpdfsdk/fpdf_attachment_embeddertest.cpp @@ -0,0 +1,260 @@ +// Copyright 2017 PDFium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include +#include +#include + +#include "public/fpdf_attachment.h" +#include "public/fpdfview.h" +#include "testing/embedder_test.h" + +static constexpr char kDateKey[] = "CreationDate"; +static constexpr char kChecksumKey[] = "CheckSum"; + +class FPDFAttachmentEmbeddertest : public EmbedderTest {}; + +TEST_F(FPDFAttachmentEmbeddertest, ExtractAttachments) { + // Open a file with two attachments. + ASSERT_TRUE(OpenDocument("embedded_attachments.pdf")); + EXPECT_EQ(2, FPDFDoc_GetAttachmentCount(document())); + + // Retrieve the first attachment. + FPDF_ATTACHMENT attachment = FPDFDoc_GetAttachment(document(), 0); + ASSERT_TRUE(attachment); + + // Check that the name of the first attachment is correct. + unsigned long len = FPDFAttachment_GetName(attachment, nullptr, 0); + std::vector buf(len); + EXPECT_EQ(12u, FPDFAttachment_GetName(attachment, buf.data(), len)); + EXPECT_STREQ(L"1.txt", + GetPlatformWString(reinterpret_cast(buf.data())) + .c_str()); + + // Check that the content of the first attachment is correct. + len = FPDFAttachment_GetFile(attachment, nullptr, 0); + buf.clear(); + buf.resize(len); + ASSERT_EQ(4u, FPDFAttachment_GetFile(attachment, buf.data(), len)); + EXPECT_EQ(std::string("test"), std::string(buf.data(), 4)); + + // Check that a non-existent key does not exist. + EXPECT_FALSE(FPDFAttachment_HasKey(attachment, "none")); + + // Check that the string value of a non-string dictionary entry is empty. + static constexpr char kSizeKey[] = "Size"; + EXPECT_EQ(FPDF_OBJECT_NUMBER, + FPDFAttachment_GetValueType(attachment, kSizeKey)); + EXPECT_EQ(2u, + FPDFAttachment_GetStringValue(attachment, kSizeKey, nullptr, 0)); + + // Check that the creation date of the first attachment is correct. + len = FPDFAttachment_GetStringValue(attachment, kDateKey, nullptr, 0); + buf.clear(); + buf.resize(len); + EXPECT_EQ(48u, FPDFAttachment_GetStringValue(attachment, kDateKey, buf.data(), + len)); + EXPECT_STREQ(L"D:20170712214438-07'00'", + GetPlatformWString(reinterpret_cast(buf.data())) + .c_str()); + + // Retrieve the second attachment. + attachment = FPDFDoc_GetAttachment(document(), 1); + ASSERT_TRUE(attachment); + + // Retrieve the second attachment file. + len = FPDFAttachment_GetFile(attachment, nullptr, 0); + buf.clear(); + buf.resize(len); + EXPECT_EQ(5869u, FPDFAttachment_GetFile(attachment, buf.data(), len)); + + // Check that the calculated checksum of the file data matches expectation. + const char kCheckSum[] = "72afcddedf554dda63c0c88e06f1ce18"; + const wchar_t kCheckSumW[] = L"<72AFCDDEDF554DDA63C0C88E06F1CE18>"; + const std::string generated_checksum = + GenerateMD5Base16(reinterpret_cast(buf.data()), len); + EXPECT_EQ(kCheckSum, generated_checksum); + + // Check that the stored checksum matches expectation. + len = FPDFAttachment_GetStringValue(attachment, kChecksumKey, nullptr, 0); + buf.clear(); + buf.resize(len); + EXPECT_EQ(70u, FPDFAttachment_GetStringValue(attachment, kChecksumKey, + buf.data(), len)); + EXPECT_EQ(kCheckSumW, + GetPlatformWString(reinterpret_cast(buf.data()))); +} + +TEST_F(FPDFAttachmentEmbeddertest, AddAttachments) { + // Open a file with two attachments. + ASSERT_TRUE(OpenDocument("embedded_attachments.pdf")); + EXPECT_EQ(2, FPDFDoc_GetAttachmentCount(document())); + + // Check that adding an attachment with an empty name would fail. + EXPECT_FALSE(FPDFDoc_AddAttachment(document(), nullptr)); + + // Add an attachment to the beginning of the embedded file list. + std::unique_ptr file_name = + GetFPDFWideString(L"0.txt"); + FPDF_ATTACHMENT attachment = + FPDFDoc_AddAttachment(document(), file_name.get()); + + // Check that writing to a file with nullptr but non-zero bytes would fail. + EXPECT_FALSE(FPDFAttachment_SetFile(attachment, document(), nullptr, 10)); + + // Set the new attachment's file. + constexpr char kContents1[] = "Hello!"; + EXPECT_TRUE(FPDFAttachment_SetFile(attachment, document(), kContents1, + strlen(kContents1))); + + // Verify the name of the new attachment (i.e. the first attachment). + attachment = FPDFDoc_GetAttachment(document(), 0); + ASSERT_TRUE(attachment); + unsigned long len = FPDFAttachment_GetName(attachment, nullptr, 0); + std::vector buf(len); + EXPECT_EQ(12u, FPDFAttachment_GetName(attachment, buf.data(), len)); + EXPECT_STREQ(L"0.txt", + GetPlatformWString(reinterpret_cast(buf.data())) + .c_str()); + + // Verify the content of the new attachment (i.e. the first attachment). + len = FPDFAttachment_GetFile(attachment, nullptr, 0); + buf.clear(); + buf.resize(len); + ASSERT_EQ(6u, FPDFAttachment_GetFile(attachment, buf.data(), len)); + EXPECT_EQ(std::string(kContents1), std::string(buf.data(), 6)); + + // Add an attachment to the end of the embedded file list and set its file. + file_name = GetFPDFWideString(L"z.txt"); + attachment = FPDFDoc_AddAttachment(document(), file_name.get()); + constexpr char kContents2[] = "World!"; + EXPECT_TRUE(FPDFAttachment_SetFile(attachment, document(), kContents2, + strlen(kContents2))); + EXPECT_EQ(4, FPDFDoc_GetAttachmentCount(document())); + + // Verify the name of the new attachment (i.e. the fourth attachment). + attachment = FPDFDoc_GetAttachment(document(), 3); + ASSERT_TRUE(attachment); + len = FPDFAttachment_GetName(attachment, nullptr, 0); + buf.clear(); + buf.resize(len); + EXPECT_EQ(12u, FPDFAttachment_GetName(attachment, buf.data(), len)); + EXPECT_STREQ(L"z.txt", + GetPlatformWString(reinterpret_cast(buf.data())) + .c_str()); + + // Verify the content of the new attachment (i.e. the fourth attachment). + len = FPDFAttachment_GetFile(attachment, nullptr, 0); + buf.clear(); + buf.resize(len); + ASSERT_EQ(6u, FPDFAttachment_GetFile(attachment, buf.data(), len)); + EXPECT_EQ(std::string(kContents2), std::string(buf.data(), 6)); +} + +TEST_F(FPDFAttachmentEmbeddertest, AddAttachmentsWithParams) { + // Open a file with two attachments. + ASSERT_TRUE(OpenDocument("embedded_attachments.pdf")); + EXPECT_EQ(2, FPDFDoc_GetAttachmentCount(document())); + + // Add an attachment to the embedded file list. + std::unique_ptr file_name = + GetFPDFWideString(L"5.txt"); + FPDF_ATTACHMENT attachment = + FPDFDoc_AddAttachment(document(), file_name.get()); + constexpr char kContents[] = "Hello World!"; + EXPECT_TRUE(FPDFAttachment_SetFile(attachment, document(), kContents, + strlen(kContents))); + + // Set the date to be an arbitrary value. + constexpr wchar_t kDateW[] = L"D:20170720161527-04'00'"; + std::unique_ptr ws_date = + GetFPDFWideString(kDateW); + EXPECT_TRUE( + FPDFAttachment_SetStringValue(attachment, kDateKey, ws_date.get())); + + // Set the checksum to be an arbitrary value. + constexpr wchar_t kCheckSumW[] = L""; + std::unique_ptr ws_checksum = + GetFPDFWideString(kCheckSumW); + EXPECT_TRUE(FPDFAttachment_SetStringValue(attachment, kChecksumKey, + ws_checksum.get())); + + // Verify the name of the new attachment (i.e. the second attachment). + attachment = FPDFDoc_GetAttachment(document(), 1); + ASSERT_TRUE(attachment); + unsigned long len = FPDFAttachment_GetName(attachment, nullptr, 0); + std::vector buf(len); + EXPECT_EQ(12u, FPDFAttachment_GetName(attachment, buf.data(), len)); + EXPECT_STREQ(L"5.txt", + GetPlatformWString(reinterpret_cast(buf.data())) + .c_str()); + + // Verify the content of the new attachment. + len = FPDFAttachment_GetFile(attachment, nullptr, 0); + buf.clear(); + buf.resize(len); + ASSERT_EQ(12u, FPDFAttachment_GetFile(attachment, buf.data(), len)); + EXPECT_EQ(std::string(kContents), std::string(buf.data(), 12)); + + // Verify the creation date of the new attachment. + len = FPDFAttachment_GetStringValue(attachment, kDateKey, nullptr, 0); + buf.clear(); + buf.resize(len); + EXPECT_EQ(48u, FPDFAttachment_GetStringValue(attachment, kDateKey, buf.data(), + len)); + EXPECT_STREQ(kDateW, + GetPlatformWString(reinterpret_cast(buf.data())) + .c_str()); + + // Verify the checksum of the new attachment. + len = FPDFAttachment_GetStringValue(attachment, kChecksumKey, nullptr, 0); + buf.clear(); + buf.resize(len); + EXPECT_EQ(70u, FPDFAttachment_GetStringValue(attachment, kChecksumKey, + buf.data(), len)); + EXPECT_STREQ(kCheckSumW, + GetPlatformWString(reinterpret_cast(buf.data())) + .c_str()); + + // Overwrite the existing file with empty content, and check that the checksum + // gets updated to the correct value. + EXPECT_TRUE(FPDFAttachment_SetFile(attachment, document(), nullptr, 0)); + EXPECT_EQ(0u, FPDFAttachment_GetFile(attachment, nullptr, 0)); + len = FPDFAttachment_GetStringValue(attachment, kChecksumKey, nullptr, 0); + buf.clear(); + buf.resize(len); + EXPECT_EQ(70u, FPDFAttachment_GetStringValue(attachment, kChecksumKey, + buf.data(), len)); + EXPECT_EQ(L"", + GetPlatformWString(reinterpret_cast(buf.data()))); +} + +TEST_F(FPDFAttachmentEmbeddertest, DeleteAttachment) { + // Open a file with two attachments. + ASSERT_TRUE(OpenDocument("embedded_attachments.pdf")); + EXPECT_EQ(2, FPDFDoc_GetAttachmentCount(document())); + + // Verify the name of the first attachment. + FPDF_ATTACHMENT attachment = FPDFDoc_GetAttachment(document(), 0); + unsigned long len = FPDFAttachment_GetName(attachment, nullptr, 0); + std::vector buf(len); + EXPECT_EQ(12u, FPDFAttachment_GetName(attachment, buf.data(), len)); + EXPECT_STREQ(L"1.txt", + GetPlatformWString(reinterpret_cast(buf.data())) + .c_str()); + + // Delete the first attachment. + EXPECT_TRUE(FPDFDoc_DeleteAttachment(document(), 0)); + EXPECT_EQ(1, FPDFDoc_GetAttachmentCount(document())); + + // Verify the name of the new first attachment. + attachment = FPDFDoc_GetAttachment(document(), 0); + len = FPDFAttachment_GetName(attachment, nullptr, 0); + buf.clear(); + buf.resize(len); + EXPECT_EQ(26u, FPDFAttachment_GetName(attachment, buf.data(), len)); + EXPECT_STREQ(L"attached.pdf", + GetPlatformWString(reinterpret_cast(buf.data())) + .c_str()); +} diff --git a/fpdfsdk/fpdf_catalog.cpp b/fpdfsdk/fpdf_catalog.cpp new file mode 100644 index 0000000000..af6e40a0ad --- /dev/null +++ b/fpdfsdk/fpdf_catalog.cpp @@ -0,0 +1,22 @@ +// Copyright 2017 PDFium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "public/fpdf_catalog.h" + +#include "core/fpdfapi/parser/cpdf_document.h" +#include "fpdfsdk/cpdfsdk_helpers.h" + +FPDF_EXPORT FPDF_BOOL FPDF_CALLCONV +FPDFCatalog_IsTagged(FPDF_DOCUMENT document) { + CPDF_Document* pDoc = CPDFDocumentFromFPDFDocument(document); + if (!pDoc) + return false; + + const CPDF_Dictionary* pCatalog = pDoc->GetRoot(); + if (!pCatalog) + return false; + + const CPDF_Dictionary* pMarkInfo = pCatalog->GetDictFor("MarkInfo"); + return pMarkInfo && pMarkInfo->GetIntegerFor("Marked") != 0; +} diff --git a/fpdfsdk/fpdf_catalog_unittest.cpp b/fpdfsdk/fpdf_catalog_unittest.cpp new file mode 100644 index 0000000000..b00748b49d --- /dev/null +++ b/fpdfsdk/fpdf_catalog_unittest.cpp @@ -0,0 +1,97 @@ +// Copyright 2017 PDFium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "public/fpdf_catalog.h" + +#include + +#include "core/fpdfapi/cpdf_modulemgr.h" +#include "core/fpdfapi/parser/cpdf_document.h" +#include "core/fpdfapi/parser/cpdf_number.h" +#include "core/fpdfapi/parser/cpdf_parser.h" +#include "core/fpdfapi/parser/cpdf_string.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "testing/test_support.h" + +#ifdef PDF_ENABLE_XFA +#include "fpdfsdk/fpdfxfa/cpdfxfa_context.h" +#endif // PDF_ENABLE_XFA + +class CPDF_TestDocument : public CPDF_Document { + public: + CPDF_TestDocument() : CPDF_Document(nullptr) {} + + void SetRoot(CPDF_Dictionary* root) { + m_pRootDict = root; + GetRoot(); + } +}; + +#ifdef PDF_ENABLE_XFA +class CPDF_TestXFAContext : public CPDFXFA_Context { + public: + CPDF_TestXFAContext() + : CPDFXFA_Context(pdfium::MakeUnique()) {} + + void SetRoot(CPDF_Dictionary* root) { + reinterpret_cast(GetPDFDoc())->SetRoot(root); + } + + CPDF_IndirectObjectHolder* GetHolder() { return GetPDFDoc(); } +}; +using CPDF_TestPdfDocument = CPDF_TestXFAContext; +#else // PDF_ENABLE_XFA +using CPDF_TestPdfDocument = CPDF_TestDocument; +#endif // PDF_ENABLE_XFA + +class PDFCatalogTest : public testing::Test { + public: + void SetUp() override { + CPDF_ModuleMgr::Get()->Init(); + + m_pDoc = pdfium::MakeUnique(); + + // Setup the root directory. + m_pRootObj = pdfium::MakeUnique(); + } + + void TearDown() override { + m_pDoc.reset(); + CPDF_ModuleMgr::Destroy(); + } + + protected: + std::unique_ptr m_pDoc; + std::unique_ptr m_pRootObj; +}; + +TEST_F(PDFCatalogTest, IsTagged) { + // Null doc + EXPECT_FALSE(FPDFCatalog_IsTagged(nullptr)); + + // No root + m_pDoc->SetRoot(nullptr); + EXPECT_FALSE(FPDFCatalog_IsTagged(m_pDoc.get())); + + // Empty root + m_pDoc->SetRoot(m_pRootObj.get()); + EXPECT_FALSE(FPDFCatalog_IsTagged(m_pDoc.get())); + + // Root with other key + m_pRootObj->SetNewFor("OTHER_KEY", "other value", false); + EXPECT_FALSE(FPDFCatalog_IsTagged(m_pDoc.get())); + + // Root with empty MarkInfo + CPDF_Dictionary* markInfoDict = + m_pRootObj->SetNewFor("MarkInfo"); + EXPECT_FALSE(FPDFCatalog_IsTagged(m_pDoc.get())); + + // MarkInfo present but Marked is 0 + markInfoDict->SetNewFor("Marked", 0); + EXPECT_FALSE(FPDFCatalog_IsTagged(m_pDoc.get())); + + // MarkInfo present and Marked is 1, PDF is considered tagged. + markInfoDict->SetNewFor("Marked", 1); + EXPECT_TRUE(FPDFCatalog_IsTagged(m_pDoc.get())); +} diff --git a/fpdfsdk/fpdf_doc.cpp b/fpdfsdk/fpdf_doc.cpp new file mode 100644 index 0000000000..d8be12066a --- /dev/null +++ b/fpdfsdk/fpdf_doc.cpp @@ -0,0 +1,429 @@ +// Copyright 2014 PDFium Authors. All rights reserved. +// 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_doc.h" + +#include +#include + +#include "core/fpdfapi/page/cpdf_page.h" +#include "core/fpdfapi/parser/cpdf_array.h" +#include "core/fpdfapi/parser/cpdf_document.h" +#include "core/fpdfdoc/cpdf_bookmark.h" +#include "core/fpdfdoc/cpdf_bookmarktree.h" +#include "core/fpdfdoc/cpdf_dest.h" +#include "core/fpdfdoc/cpdf_pagelabel.h" +#include "fpdfsdk/cpdfsdk_helpers.h" +#include "third_party/base/ptr_util.h" +#include "third_party/base/stl_util.h" + +namespace { + +CPDF_Bookmark FindBookmark(const CPDF_BookmarkTree& tree, + CPDF_Bookmark bookmark, + const WideString& title, + std::set* visited) { + // Return if already checked to avoid circular calling. + if (pdfium::ContainsKey(*visited, bookmark.GetDict())) + return CPDF_Bookmark(); + visited->insert(bookmark.GetDict()); + + if (bookmark.GetDict() && + bookmark.GetTitle().CompareNoCase(title.c_str()) == 0) { + // First check this item. + return bookmark; + } + + // Go into children items. + CPDF_Bookmark child = tree.GetFirstChild(bookmark); + while (child.GetDict() && !pdfium::ContainsKey(*visited, child.GetDict())) { + // Check this item and its children. + CPDF_Bookmark found = FindBookmark(tree, child, title, visited); + if (found.GetDict()) + return found; + child = tree.GetNextSibling(child); + } + return CPDF_Bookmark(); +} + +CPDF_LinkList* GetLinkList(CPDF_Page* page) { + if (!page) + return nullptr; + + CPDF_Document* pDoc = page->m_pDocument.Get(); + std::unique_ptr* pHolder = pDoc->LinksContext(); + if (!pHolder->get()) + *pHolder = pdfium::MakeUnique(); + return pHolder->get(); +} + +CPDF_Array* CPDFArrayFromDest(FPDF_DEST dest) { + return static_cast(dest); +} + +CPDF_Dictionary* CPDFDictionaryFromFPDFAction(FPDF_ACTION action) { + return ToDictionary(static_cast(action)); +} + +CPDF_Dictionary* CPDFDictionaryFromFPDFBookmark(FPDF_BOOKMARK bookmark) { + return ToDictionary(static_cast(bookmark)); +} + +CPDF_Dictionary* CPDFDictionaryFromFPDFLink(FPDF_LINK link) { + return ToDictionary(static_cast(link)); +} + +} // namespace + +FPDF_EXPORT FPDF_BOOKMARK FPDF_CALLCONV +FPDFBookmark_GetFirstChild(FPDF_DOCUMENT document, FPDF_BOOKMARK pDict) { + CPDF_Document* pDoc = CPDFDocumentFromFPDFDocument(document); + if (!pDoc) + return nullptr; + CPDF_BookmarkTree tree(pDoc); + CPDF_Bookmark bookmark(CPDFDictionaryFromFPDFBookmark(pDict)); + return tree.GetFirstChild(bookmark).GetDict(); +} + +FPDF_EXPORT FPDF_BOOKMARK FPDF_CALLCONV +FPDFBookmark_GetNextSibling(FPDF_DOCUMENT document, FPDF_BOOKMARK pDict) { + if (!pDict) + return nullptr; + CPDF_Document* pDoc = CPDFDocumentFromFPDFDocument(document); + if (!pDoc) + return nullptr; + CPDF_BookmarkTree tree(pDoc); + CPDF_Bookmark bookmark(CPDFDictionaryFromFPDFBookmark(pDict)); + return tree.GetNextSibling(bookmark).GetDict(); +} + +FPDF_EXPORT unsigned long FPDF_CALLCONV +FPDFBookmark_GetTitle(FPDF_BOOKMARK pDict, void* buffer, unsigned long buflen) { + if (!pDict) + return 0; + CPDF_Bookmark bookmark(CPDFDictionaryFromFPDFBookmark(pDict)); + WideString title = bookmark.GetTitle(); + return Utf16EncodeMaybeCopyAndReturnLength(title, buffer, buflen); +} + +FPDF_EXPORT FPDF_BOOKMARK FPDF_CALLCONV +FPDFBookmark_Find(FPDF_DOCUMENT document, FPDF_WIDESTRING title) { + if (!title || title[0] == 0) + return nullptr; + CPDF_Document* pDoc = CPDFDocumentFromFPDFDocument(document); + if (!pDoc) + return nullptr; + CPDF_BookmarkTree tree(pDoc); + size_t len = WideString::WStringLength(title); + WideString encodedTitle = WideString::FromUTF16LE(title, len); + std::set visited; + return FindBookmark(tree, CPDF_Bookmark(), encodedTitle, &visited).GetDict(); +} + +FPDF_EXPORT FPDF_DEST FPDF_CALLCONV FPDFBookmark_GetDest(FPDF_DOCUMENT document, + FPDF_BOOKMARK pDict) { + if (!pDict) + return nullptr; + CPDF_Document* pDoc = CPDFDocumentFromFPDFDocument(document); + if (!pDoc) + return nullptr; + CPDF_Bookmark bookmark(CPDFDictionaryFromFPDFBookmark(pDict)); + CPDF_Dest dest = bookmark.GetDest(pDoc); + if (dest.GetObject()) + return dest.GetObject(); + // If this bookmark is not directly associated with a dest, we try to get + // action + CPDF_Action action = bookmark.GetAction(); + if (!action.GetDict()) + return nullptr; + return action.GetDest(pDoc).GetObject(); +} + +FPDF_EXPORT FPDF_ACTION FPDF_CALLCONV +FPDFBookmark_GetAction(FPDF_BOOKMARK pDict) { + if (!pDict) + return nullptr; + CPDF_Bookmark bookmark(CPDFDictionaryFromFPDFBookmark(pDict)); + return bookmark.GetAction().GetDict(); +} + +FPDF_EXPORT unsigned long FPDF_CALLCONV FPDFAction_GetType(FPDF_ACTION pDict) { + if (!pDict) + return PDFACTION_UNSUPPORTED; + + CPDF_Action action(CPDFDictionaryFromFPDFAction(pDict)); + CPDF_Action::ActionType type = action.GetType(); + switch (type) { + case CPDF_Action::GoTo: + return PDFACTION_GOTO; + case CPDF_Action::GoToR: + return PDFACTION_REMOTEGOTO; + case CPDF_Action::URI: + return PDFACTION_URI; + case CPDF_Action::Launch: + return PDFACTION_LAUNCH; + default: + return PDFACTION_UNSUPPORTED; + } +} + +FPDF_EXPORT FPDF_DEST FPDF_CALLCONV FPDFAction_GetDest(FPDF_DOCUMENT document, + FPDF_ACTION pDict) { + if (!pDict) + return nullptr; + CPDF_Document* pDoc = CPDFDocumentFromFPDFDocument(document); + if (!pDoc) + return nullptr; + CPDF_Action action(CPDFDictionaryFromFPDFAction(pDict)); + return action.GetDest(pDoc).GetObject(); +} + +FPDF_EXPORT unsigned long FPDF_CALLCONV +FPDFAction_GetFilePath(FPDF_ACTION pDict, void* buffer, unsigned long buflen) { + unsigned long type = FPDFAction_GetType(pDict); + if (type != PDFACTION_REMOTEGOTO && type != PDFACTION_LAUNCH) + return 0; + + CPDF_Action action(CPDFDictionaryFromFPDFAction(pDict)); + ByteString path = action.GetFilePath().UTF8Encode(); + unsigned long len = path.GetLength() + 1; + if (buffer && len <= buflen) + memcpy(buffer, path.c_str(), len); + return len; +} + +FPDF_EXPORT unsigned long FPDF_CALLCONV +FPDFAction_GetURIPath(FPDF_DOCUMENT document, + FPDF_ACTION pDict, + void* buffer, + unsigned long buflen) { + if (!pDict) + return 0; + CPDF_Document* pDoc = CPDFDocumentFromFPDFDocument(document); + if (!pDoc) + return 0; + CPDF_Action action(CPDFDictionaryFromFPDFAction(pDict)); + ByteString path = action.GetURI(pDoc); + unsigned long len = path.GetLength() + 1; + if (buffer && len <= buflen) + memcpy(buffer, path.c_str(), len); + return len; +} + +FPDF_EXPORT unsigned long FPDF_CALLCONV +FPDFDest_GetPageIndex(FPDF_DOCUMENT document, FPDF_DEST dest) { + if (!dest) + return 0; + + CPDF_Document* pDoc = CPDFDocumentFromFPDFDocument(document); + if (!pDoc) + return 0; + + CPDF_Dest destination(CPDFArrayFromDest(dest)); + return destination.GetPageIndexDeprecated(pDoc); +} + +FPDF_EXPORT int FPDF_CALLCONV FPDFDest_GetDestPageIndex(FPDF_DOCUMENT document, + FPDF_DEST dest) { + if (!dest) + return -1; + + CPDF_Document* pDoc = CPDFDocumentFromFPDFDocument(document); + if (!pDoc) + return -1; + + CPDF_Dest destination(CPDFArrayFromDest(dest)); + return destination.GetDestPageIndex(pDoc); +} + +FPDF_EXPORT unsigned long FPDF_CALLCONV +FPDFDest_GetView(FPDF_DEST pDict, + unsigned long* pNumParams, + FS_FLOAT* pParams) { + if (!pDict) { + *pNumParams = 0; + return 0; + } + + CPDF_Dest destination(CPDFArrayFromDest(pDict)); + unsigned long nParams = destination.GetNumParams(); + ASSERT(nParams <= 4); + *pNumParams = nParams; + for (unsigned long i = 0; i < nParams; ++i) + pParams[i] = destination.GetParam(i); + return destination.GetZoomMode(); +} + +FPDF_EXPORT FPDF_BOOL FPDF_CALLCONV +FPDFDest_GetLocationInPage(FPDF_DEST pDict, + FPDF_BOOL* hasXVal, + FPDF_BOOL* hasYVal, + FPDF_BOOL* hasZoomVal, + FS_FLOAT* x, + FS_FLOAT* y, + FS_FLOAT* zoom) { + if (!pDict) + return false; + + auto dest = pdfium::MakeUnique(CPDFArrayFromDest(pDict)); + + // FPDF_BOOL is an int, GetXYZ expects bools. + bool bHasX; + bool bHasY; + bool bHasZoom; + if (!dest->GetXYZ(&bHasX, &bHasY, &bHasZoom, x, y, zoom)) + return false; + + *hasXVal = bHasX; + *hasYVal = bHasY; + *hasZoomVal = bHasZoom; + return true; +} + +FPDF_EXPORT FPDF_LINK FPDF_CALLCONV FPDFLink_GetLinkAtPoint(FPDF_PAGE page, + double x, + double y) { + CPDF_Page* pPage = CPDFPageFromFPDFPage(page); + if (!pPage) + return nullptr; + + CPDF_LinkList* pLinkList = GetLinkList(pPage); + if (!pLinkList) + return nullptr; + + return pLinkList + ->GetLinkAtPoint(pPage, + CFX_PointF(static_cast(x), static_cast(y)), + nullptr) + .GetDict(); +} + +FPDF_EXPORT int FPDF_CALLCONV FPDFLink_GetLinkZOrderAtPoint(FPDF_PAGE page, + double x, + double y) { + CPDF_Page* pPage = CPDFPageFromFPDFPage(page); + if (!pPage) + return -1; + + CPDF_LinkList* pLinkList = GetLinkList(pPage); + if (!pLinkList) + return -1; + + int z_order = -1; + pLinkList->GetLinkAtPoint( + pPage, CFX_PointF(static_cast(x), static_cast(y)), + &z_order); + return z_order; +} + +FPDF_EXPORT FPDF_DEST FPDF_CALLCONV FPDFLink_GetDest(FPDF_DOCUMENT document, + FPDF_LINK pDict) { + if (!pDict) + return nullptr; + CPDF_Document* pDoc = CPDFDocumentFromFPDFDocument(document); + if (!pDoc) + return nullptr; + CPDF_Link link(CPDFDictionaryFromFPDFLink(pDict)); + FPDF_DEST dest = link.GetDest(pDoc).GetObject(); + if (dest) + return dest; + // If this link is not directly associated with a dest, we try to get action + CPDF_Action action = link.GetAction(); + if (!action.GetDict()) + return nullptr; + return action.GetDest(pDoc).GetObject(); +} + +FPDF_EXPORT FPDF_ACTION FPDF_CALLCONV FPDFLink_GetAction(FPDF_LINK pDict) { + if (!pDict) + return nullptr; + + CPDF_Link link(CPDFDictionaryFromFPDFLink(pDict)); + return link.GetAction().GetDict(); +} + +FPDF_EXPORT FPDF_BOOL FPDF_CALLCONV FPDFLink_Enumerate(FPDF_PAGE page, + int* start_pos, + FPDF_LINK* link_annot) { + if (!start_pos || !link_annot) + return false; + CPDF_Page* pPage = CPDFPageFromFPDFPage(page); + if (!pPage || !pPage->m_pFormDict) + return false; + CPDF_Array* pAnnots = pPage->m_pFormDict->GetArrayFor("Annots"); + if (!pAnnots) + return false; + for (size_t i = *start_pos; i < pAnnots->GetCount(); i++) { + CPDF_Dictionary* pDict = ToDictionary(pAnnots->GetDirectObjectAt(i)); + if (!pDict) + continue; + if (pDict->GetStringFor("Subtype") == "Link") { + *start_pos = static_cast(i + 1); + *link_annot = static_cast(pDict); + return true; + } + } + return false; +} + +FPDF_EXPORT FPDF_BOOL FPDF_CALLCONV FPDFLink_GetAnnotRect(FPDF_LINK link_annot, + FS_RECTF* rect) { + if (!link_annot || !rect) + return false; + CPDF_Dictionary* pAnnotDict = CPDFDictionaryFromFPDFLink(link_annot); + FSRECTFFromCFXFloatRect(pAnnotDict->GetRectFor("Rect"), rect); + return true; +} + +FPDF_EXPORT int FPDF_CALLCONV FPDFLink_CountQuadPoints(FPDF_LINK link_annot) { + const CPDF_Array* pArray = + GetQuadPointsArrayFromDictionary(CPDFDictionaryFromFPDFLink(link_annot)); + return pArray ? static_cast(pArray->GetCount() / 8) : 0; +} + +FPDF_EXPORT FPDF_BOOL FPDF_CALLCONV +FPDFLink_GetQuadPoints(FPDF_LINK link_annot, + int quad_index, + FS_QUADPOINTSF* quad_points) { + if (!quad_points || quad_index < 0) + return false; + return GetQuadPointsFromDictionary(CPDFDictionaryFromFPDFLink(link_annot), + static_cast(quad_index), + quad_points); +} + +FPDF_EXPORT unsigned long FPDF_CALLCONV FPDF_GetMetaText(FPDF_DOCUMENT document, + FPDF_BYTESTRING tag, + void* buffer, + unsigned long buflen) { + if (!tag) + return 0; + CPDF_Document* pDoc = CPDFDocumentFromFPDFDocument(document); + if (!pDoc) + return 0; + pDoc->LoadDocumentInfo(); + const CPDF_Dictionary* pInfo = pDoc->GetInfo(); + if (!pInfo) + return 0; + WideString text = pInfo->GetUnicodeTextFor(tag); + return Utf16EncodeMaybeCopyAndReturnLength(text, buffer, buflen); +} + +FPDF_EXPORT unsigned long FPDF_CALLCONV +FPDF_GetPageLabel(FPDF_DOCUMENT document, + int page_index, + void* buffer, + unsigned long buflen) { + if (page_index < 0) + return 0; + + // CPDF_PageLabel can deal with NULL |document|. + CPDF_PageLabel label(CPDFDocumentFromFPDFDocument(document)); + Optional str = label.GetLabel(page_index); + return str.has_value() + ? Utf16EncodeMaybeCopyAndReturnLength(str.value(), buffer, buflen) + : 0; +} diff --git a/fpdfsdk/fpdf_doc_embeddertest.cpp b/fpdfsdk/fpdf_doc_embeddertest.cpp new file mode 100644 index 0000000000..07b83263be --- /dev/null +++ b/fpdfsdk/fpdf_doc_embeddertest.cpp @@ -0,0 +1,424 @@ +// Copyright 2015 PDFium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include +#include + +#include "core/fxcrt/fx_string.h" +#include "public/fpdf_doc.h" +#include "public/fpdf_edit.h" +#include "public/fpdfview.h" +#include "testing/embedder_test.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "testing/test_support.h" + +class FPDFDocEmbeddertest : public EmbedderTest {}; + +TEST_F(FPDFDocEmbeddertest, DestGetPageIndex) { + EXPECT_TRUE(OpenDocument("named_dests.pdf")); + + // NULL FPDF_DEST case. + EXPECT_EQ(0U, FPDFDest_GetPageIndex(document(), nullptr)); + EXPECT_EQ(-1, FPDFDest_GetDestPageIndex(document(), nullptr)); + + // Page number directly in item from Dests NameTree. + FPDF_DEST dest = FPDF_GetNamedDestByName(document(), "First"); + EXPECT_TRUE(dest); + EXPECT_EQ(1U, FPDFDest_GetPageIndex(document(), dest)); + EXPECT_EQ(1, FPDFDest_GetDestPageIndex(document(), dest)); + + // Page number via object reference in item from Dests NameTree. + dest = FPDF_GetNamedDestByName(document(), "Next"); + EXPECT_TRUE(dest); + EXPECT_EQ(1U, FPDFDest_GetPageIndex(document(), dest)); + EXPECT_EQ(1, FPDFDest_GetDestPageIndex(document(), dest)); + + // Page number directly in item from Dests dictionary. + dest = FPDF_GetNamedDestByName(document(), "FirstAlternate"); + EXPECT_TRUE(dest); + EXPECT_EQ(11U, FPDFDest_GetPageIndex(document(), dest)); + EXPECT_EQ(11, FPDFDest_GetDestPageIndex(document(), dest)); + + // Invalid object reference in item from Dests NameTree. + dest = FPDF_GetNamedDestByName(document(), "LastAlternate"); + EXPECT_TRUE(dest); + EXPECT_EQ(0U, FPDFDest_GetPageIndex(document(), dest)); + EXPECT_EQ(-1, FPDFDest_GetDestPageIndex(document(), dest)); +} + +TEST_F(FPDFDocEmbeddertest, DestGetView) { + EXPECT_TRUE(OpenDocument("named_dests.pdf")); + + unsigned long numParams; + FS_FLOAT params[4]; + + numParams = 42; + std::fill_n(params, 4, 42.4242f); + EXPECT_EQ(static_cast(PDFDEST_VIEW_UNKNOWN_MODE), + FPDFDest_GetView(nullptr, &numParams, params)); + EXPECT_EQ(0U, numParams); + EXPECT_FLOAT_EQ(42.4242f, params[0]); + + numParams = 42; + std::fill_n(params, 4, 42.4242f); + FPDF_DEST dest = FPDF_GetNamedDestByName(document(), "First"); + EXPECT_TRUE(dest); + EXPECT_EQ(static_cast(PDFDEST_VIEW_XYZ), + FPDFDest_GetView(dest, &numParams, params)); + EXPECT_EQ(3U, numParams); + EXPECT_FLOAT_EQ(0, params[0]); + EXPECT_FLOAT_EQ(0, params[1]); + EXPECT_FLOAT_EQ(1, params[2]); + EXPECT_FLOAT_EQ(42.4242f, params[3]); + + numParams = 42; + std::fill_n(params, 4, 42.4242f); + dest = FPDF_GetNamedDestByName(document(), "Next"); + EXPECT_TRUE(dest); + EXPECT_EQ(static_cast(PDFDEST_VIEW_FIT), + FPDFDest_GetView(dest, &numParams, params)); + EXPECT_EQ(0U, numParams); + EXPECT_FLOAT_EQ(42.4242f, params[0]); + + numParams = 42; + std::fill_n(params, 4, 42.4242f); + dest = FPDF_GetNamedDestByName(document(), "FirstAlternate"); + EXPECT_TRUE(dest); + EXPECT_EQ(static_cast(PDFDEST_VIEW_XYZ), + FPDFDest_GetView(dest, &numParams, params)); + EXPECT_EQ(3U, numParams); + EXPECT_FLOAT_EQ(200, params[0]); + EXPECT_FLOAT_EQ(400, params[1]); + EXPECT_FLOAT_EQ(800, params[2]); + EXPECT_FLOAT_EQ(42.4242f, params[3]); + + numParams = 42; + std::fill_n(params, 4, 42.4242f); + dest = FPDF_GetNamedDestByName(document(), "LastAlternate"); + EXPECT_TRUE(dest); + EXPECT_EQ(static_cast(PDFDEST_VIEW_XYZ), + FPDFDest_GetView(dest, &numParams, params)); + EXPECT_EQ(3U, numParams); + EXPECT_FLOAT_EQ(0, params[0]); + EXPECT_FLOAT_EQ(0, params[1]); + EXPECT_FLOAT_EQ(-200, params[2]); + EXPECT_FLOAT_EQ(42.4242f, params[3]); +} + +TEST_F(FPDFDocEmbeddertest, DestGetLocationInPage) { + EXPECT_TRUE(OpenDocument("named_dests.pdf")); + + // NULL FPDF_DEST case. + EXPECT_EQ(0U, FPDFDest_GetPageIndex(document(), nullptr)); + EXPECT_EQ(-1, FPDFDest_GetDestPageIndex(document(), nullptr)); + + FPDF_DEST dest = FPDF_GetNamedDestByName(document(), "First"); + EXPECT_TRUE(dest); + + FPDF_BOOL hasX; + FPDF_BOOL hasY; + FPDF_BOOL hasZoom; + FS_FLOAT x; + FS_FLOAT y; + FS_FLOAT zoom; + EXPECT_TRUE( + FPDFDest_GetLocationInPage(dest, &hasX, &hasY, &hasZoom, &x, &y, &zoom)); + EXPECT_TRUE(hasX); + EXPECT_TRUE(hasY); + EXPECT_TRUE(hasZoom); + EXPECT_EQ(0, x); + EXPECT_EQ(0, y); + EXPECT_EQ(1, zoom); +} + +TEST_F(FPDFDocEmbeddertest, BUG_680376) { + EXPECT_TRUE(OpenDocument("bug_680376.pdf")); + + // Page number directly in item from Dests NameTree. + FPDF_DEST dest = FPDF_GetNamedDestByName(document(), "First"); + EXPECT_TRUE(dest); + EXPECT_EQ(static_cast(-1), + FPDFDest_GetPageIndex(document(), dest)); + EXPECT_EQ(-1, FPDFDest_GetDestPageIndex(document(), dest)); +} + +TEST_F(FPDFDocEmbeddertest, BUG_821454) { + EXPECT_TRUE(OpenDocument("bug_821454.pdf")); + + FPDF_PAGE page = LoadPage(0); + ASSERT_TRUE(page); + + FPDF_LINK link1 = FPDFLink_GetLinkAtPoint(page, 150, 360); + ASSERT_TRUE(link1); + FPDF_LINK link2 = FPDFLink_GetLinkAtPoint(page, 150, 420); + ASSERT_TRUE(link2); + + FPDF_DEST dest1 = FPDFLink_GetDest(document(), link1); + ASSERT_TRUE(dest1); + FPDF_DEST dest2 = FPDFLink_GetDest(document(), link2); + ASSERT_TRUE(dest2); + + EXPECT_EQ(0, FPDFDest_GetDestPageIndex(document(), dest1)); + EXPECT_EQ(0, FPDFDest_GetDestPageIndex(document(), dest2)); + + { + FPDF_BOOL has_x_coord; + FPDF_BOOL has_y_coord; + FPDF_BOOL has_zoom; + FS_FLOAT x; + FS_FLOAT y; + FS_FLOAT zoom; + FPDF_BOOL success = FPDFDest_GetLocationInPage( + dest1, &has_x_coord, &has_y_coord, &has_zoom, &x, &y, &zoom); + ASSERT_TRUE(success); + EXPECT_TRUE(has_x_coord); + EXPECT_TRUE(has_y_coord); + EXPECT_FALSE(has_zoom); + EXPECT_FLOAT_EQ(100.0f, x); + EXPECT_FLOAT_EQ(200.0f, y); + } + { + FPDF_BOOL has_x_coord; + FPDF_BOOL has_y_coord; + FPDF_BOOL has_zoom; + FS_FLOAT x; + FS_FLOAT y; + FS_FLOAT zoom; + FPDF_BOOL success = FPDFDest_GetLocationInPage( + dest2, &has_x_coord, &has_y_coord, &has_zoom, &x, &y, &zoom); + ASSERT_TRUE(success); + EXPECT_TRUE(has_x_coord); + EXPECT_TRUE(has_y_coord); + EXPECT_FALSE(has_zoom); + EXPECT_FLOAT_EQ(150.0f, x); + EXPECT_FLOAT_EQ(250.0f, y); + } + + UnloadPage(page); +} + +TEST_F(FPDFDocEmbeddertest, ActionGetFilePath) { + EXPECT_TRUE(OpenDocument("launch_action.pdf")); + + FPDF_PAGE page = LoadPage(0); + ASSERT_TRUE(page); + + // The target action is nearly the size of the whole page. + FPDF_LINK link = FPDFLink_GetLinkAtPoint(page, 100, 100); + ASSERT_TRUE(link); + + FPDF_ACTION action = FPDFLink_GetAction(link); + ASSERT_TRUE(action); + + const char kExpectedResult[] = "test.pdf"; + const unsigned long kExpectedLength = sizeof(kExpectedResult); + unsigned long bufsize = FPDFAction_GetFilePath(action, nullptr, 0); + ASSERT_EQ(kExpectedLength, bufsize); + + char buf[kExpectedLength]; + EXPECT_EQ(bufsize, FPDFAction_GetFilePath(action, buf, bufsize)); + EXPECT_EQ(std::string(kExpectedResult), std::string(buf)); + + UnloadPage(page); +} + +TEST_F(FPDFDocEmbeddertest, NoBookmarks) { + // Open a file with no bookmarks. + EXPECT_TRUE(OpenDocument("named_dests.pdf")); + + // The non-existent top-level bookmark has no title. + unsigned short buf[128]; + EXPECT_EQ(0u, FPDFBookmark_GetTitle(nullptr, buf, sizeof(buf))); + + // The non-existent top-level bookmark has no children. + EXPECT_EQ(nullptr, FPDFBookmark_GetFirstChild(document(), nullptr)); +} + +TEST_F(FPDFDocEmbeddertest, Bookmarks) { + // Open a file with two bookmarks. + EXPECT_TRUE(OpenDocument("bookmarks.pdf")); + + // The existent top-level bookmark has no title. + unsigned short buf[128]; + EXPECT_EQ(0u, FPDFBookmark_GetTitle(nullptr, buf, sizeof(buf))); + + FPDF_BOOKMARK child = FPDFBookmark_GetFirstChild(document(), nullptr); + EXPECT_TRUE(child); + EXPECT_EQ(34u, FPDFBookmark_GetTitle(child, buf, sizeof(buf))); + EXPECT_EQ(WideString(L"A Good Beginning"), WideString::FromUTF16LE(buf, 16)); + + EXPECT_EQ(nullptr, FPDFBookmark_GetFirstChild(document(), child)); + + FPDF_BOOKMARK sibling = FPDFBookmark_GetNextSibling(document(), child); + EXPECT_TRUE(sibling); + EXPECT_EQ(28u, FPDFBookmark_GetTitle(sibling, buf, sizeof(buf))); + EXPECT_EQ(WideString(L"A Good Ending"), WideString::FromUTF16LE(buf, 13)); + + EXPECT_EQ(nullptr, FPDFBookmark_GetNextSibling(document(), sibling)); +} + +TEST_F(FPDFDocEmbeddertest, FindBookmarks) { + // Open a file with two bookmarks. + EXPECT_TRUE(OpenDocument("bookmarks.pdf")); + + // Find the first one, based on its known title. + std::unique_ptr title = + GetFPDFWideString(L"A Good Beginning"); + FPDF_BOOKMARK child = FPDFBookmark_Find(document(), title.get()); + EXPECT_TRUE(child); + + // Check that the string matches. + unsigned short buf[128]; + EXPECT_EQ(34u, FPDFBookmark_GetTitle(child, buf, sizeof(buf))); + EXPECT_EQ(WideString(L"A Good Beginning"), WideString::FromUTF16LE(buf, 16)); + + // Check that it is them same as the one returned by GetFirstChild. + EXPECT_EQ(child, FPDFBookmark_GetFirstChild(document(), nullptr)); + + // Try to find one using a non-existent title. + std::unique_ptr bad_title = + GetFPDFWideString(L"A BAD Beginning"); + EXPECT_EQ(nullptr, FPDFBookmark_Find(document(), bad_title.get())); +} + +// Check circular bookmarks will not cause infinite loop. +TEST_F(FPDFDocEmbeddertest, FindBookmarks_bug420) { + // Open a file with circular bookmarks. + EXPECT_TRUE(OpenDocument("bookmarks_circular.pdf")); + + // Try to find a title. + std::unique_ptr title = + GetFPDFWideString(L"anything"); + EXPECT_EQ(nullptr, FPDFBookmark_Find(document(), title.get())); +} + +TEST_F(FPDFDocEmbeddertest, DeletePage) { + EXPECT_TRUE(OpenDocument("hello_world.pdf")); + EXPECT_EQ(1, FPDF_GetPageCount(document())); + FPDFPage_Delete(document(), 0); + EXPECT_EQ(0, FPDF_GetPageCount(document())); +} + +TEST_F(FPDFDocEmbeddertest, GetMetaText) { + ASSERT_TRUE(OpenDocument("bug_601362.pdf")); + + // Invalid document / tag results in 0. + unsigned short buf[128]; + EXPECT_EQ(0u, FPDF_GetMetaText(document(), nullptr, buf, sizeof(buf))); + EXPECT_EQ(0u, FPDF_GetMetaText(nullptr, "", buf, sizeof(buf))); + + // Tags that do not eixst results in an empty wide string. + EXPECT_EQ(2u, FPDF_GetMetaText(document(), "", buf, sizeof(buf))); + EXPECT_EQ(2u, FPDF_GetMetaText(document(), "foo", buf, sizeof(buf))); + ASSERT_EQ(2u, FPDF_GetMetaText(document(), "Title", buf, sizeof(buf))); + ASSERT_EQ(2u, FPDF_GetMetaText(document(), "Author", buf, sizeof(buf))); + ASSERT_EQ(2u, FPDF_GetMetaText(document(), "Subject", buf, sizeof(buf))); + ASSERT_EQ(2u, FPDF_GetMetaText(document(), "Keywords", buf, sizeof(buf))); + ASSERT_EQ(2u, FPDF_GetMetaText(document(), "Producer", buf, sizeof(buf))); + + constexpr wchar_t kExpectedCreator[] = L"Microsoft Word"; + ASSERT_EQ(30u, FPDF_GetMetaText(document(), "Creator", buf, sizeof(buf))); + EXPECT_EQ(WideString(kExpectedCreator), + WideString::FromUTF16LE(buf, FXSYS_len(kExpectedCreator))); + + constexpr wchar_t kExpectedCreationDate[] = L"D:20160411190039+00'00'"; + ASSERT_EQ(48u, + FPDF_GetMetaText(document(), "CreationDate", buf, sizeof(buf))); + EXPECT_EQ(WideString(kExpectedCreationDate), + WideString::FromUTF16LE(buf, FXSYS_len(kExpectedCreationDate))); + + constexpr wchar_t kExpectedModDate[] = L"D:20160411190039+00'00'"; + ASSERT_EQ(48u, FPDF_GetMetaText(document(), "ModDate", buf, sizeof(buf))); + EXPECT_EQ(WideString(kExpectedModDate), + WideString::FromUTF16LE(buf, FXSYS_len(kExpectedModDate))); +} + +TEST_F(FPDFDocEmbeddertest, GetMetaTextSameObjectNumber) { + ASSERT_TRUE(OpenDocument("annotation_highlight_square_with_ap.pdf")); + + // The PDF has been edited. It has two %%EOF markers, and 2 objects numbered + // (1 0). Both objects are /Info dictionaries, but contain different data. + // Make sure ModDate is the date of the last modification. + unsigned short buf[128]; + constexpr wchar_t kExpectedModDate[] = L"D:20170612232940-04'00'"; + ASSERT_EQ(48u, FPDF_GetMetaText(document(), "ModDate", buf, sizeof(buf))); + EXPECT_EQ(WideString(kExpectedModDate), + WideString::FromUTF16LE(buf, FXSYS_len(kExpectedModDate))); +} + +TEST_F(FPDFDocEmbeddertest, GetMetaTextInAttachmentFile) { + ASSERT_TRUE(OpenDocument("embedded_attachments.pdf")); + + // Make sure this is the date from the PDF itself and not the attached PDF. + unsigned short buf[128]; + constexpr wchar_t kExpectedModDate[] = L"D:20170712214448-07'00'"; + ASSERT_EQ(48u, FPDF_GetMetaText(document(), "ModDate", buf, sizeof(buf))); + EXPECT_EQ(WideString(kExpectedModDate), + WideString::FromUTF16LE(buf, FXSYS_len(kExpectedModDate))); +} + +TEST_F(FPDFDocEmbeddertest, GetMetaTextFromNewDocument) { + FPDF_DOCUMENT empty_doc = FPDF_CreateNewDocument(); + unsigned short buf[128]; + EXPECT_EQ(2u, FPDF_GetMetaText(empty_doc, "Title", buf, sizeof(buf))); + FPDF_CloseDocument(empty_doc); +} + +TEST_F(FPDFDocEmbeddertest, NoPageLabels) { + EXPECT_TRUE(OpenDocument("about_blank.pdf")); + EXPECT_EQ(1, FPDF_GetPageCount(document())); + + ASSERT_EQ(0u, FPDF_GetPageLabel(document(), 0, nullptr, 0)); +} + +TEST_F(FPDFDocEmbeddertest, GetPageLabels) { + EXPECT_TRUE(OpenDocument("page_labels.pdf")); + EXPECT_EQ(7, FPDF_GetPageCount(document())); + + // We do not request labels, when use FPDFAvail_IsXXXAvail. + // Flush all data, to allow read labels. + SetWholeFileAvailable(); + + unsigned short buf[128]; + EXPECT_EQ(0u, FPDF_GetPageLabel(document(), -2, buf, sizeof(buf))); + EXPECT_EQ(0u, FPDF_GetPageLabel(document(), -1, buf, sizeof(buf))); + + const wchar_t kExpectedPageLabel0[] = L"i"; + ASSERT_EQ(4u, FPDF_GetPageLabel(document(), 0, buf, sizeof(buf))); + EXPECT_EQ(WideString(kExpectedPageLabel0), + WideString::FromUTF16LE(buf, FXSYS_len(kExpectedPageLabel0))); + + const wchar_t kExpectedPageLabel1[] = L"ii"; + ASSERT_EQ(6u, FPDF_GetPageLabel(document(), 1, buf, sizeof(buf))); + EXPECT_EQ(WideString(kExpectedPageLabel1), + WideString::FromUTF16LE(buf, FXSYS_len(kExpectedPageLabel1))); + + const wchar_t kExpectedPageLabel2[] = L"1"; + ASSERT_EQ(4u, FPDF_GetPageLabel(document(), 2, buf, sizeof(buf))); + EXPECT_EQ(WideString(kExpectedPageLabel2), + WideString::FromUTF16LE(buf, FXSYS_len(kExpectedPageLabel2))); + + const wchar_t kExpectedPageLabel3[] = L"2"; + ASSERT_EQ(4u, FPDF_GetPageLabel(document(), 3, buf, sizeof(buf))); + EXPECT_EQ(WideString(kExpectedPageLabel3), + WideString::FromUTF16LE(buf, FXSYS_len(kExpectedPageLabel3))); + + const wchar_t kExpectedPageLabel4[] = L"zzA"; + ASSERT_EQ(8u, FPDF_GetPageLabel(document(), 4, buf, sizeof(buf))); + EXPECT_EQ(WideString(kExpectedPageLabel4), + WideString::FromUTF16LE(buf, FXSYS_len(kExpectedPageLabel4))); + + const wchar_t kExpectedPageLabel5[] = L"zzB"; + ASSERT_EQ(8u, FPDF_GetPageLabel(document(), 5, buf, sizeof(buf))); + EXPECT_EQ(WideString(kExpectedPageLabel5), + WideString::FromUTF16LE(buf, FXSYS_len(kExpectedPageLabel5))); + + const wchar_t kExpectedPageLabel6[] = L""; + ASSERT_EQ(2u, FPDF_GetPageLabel(document(), 6, buf, sizeof(buf))); + EXPECT_EQ(WideString(kExpectedPageLabel6), + WideString::FromUTF16LE(buf, FXSYS_len(kExpectedPageLabel6))); + + ASSERT_EQ(0u, FPDF_GetPageLabel(document(), 7, buf, sizeof(buf))); + ASSERT_EQ(0u, FPDF_GetPageLabel(document(), 8, buf, sizeof(buf))); +} diff --git a/fpdfsdk/fpdf_doc_unittest.cpp b/fpdfsdk/fpdf_doc_unittest.cpp new file mode 100644 index 0000000000..b52cccf098 --- /dev/null +++ b/fpdfsdk/fpdf_doc_unittest.cpp @@ -0,0 +1,271 @@ +// Copyright 2016 PDFium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "public/fpdf_doc.h" + +#include +#include + +#include "core/fpdfapi/cpdf_modulemgr.h" +#include "core/fpdfapi/parser/cpdf_array.h" +#include "core/fpdfapi/parser/cpdf_document.h" +#include "core/fpdfapi/parser/cpdf_name.h" +#include "core/fpdfapi/parser/cpdf_null.h" +#include "core/fpdfapi/parser/cpdf_number.h" +#include "core/fpdfapi/parser/cpdf_parser.h" +#include "core/fpdfapi/parser/cpdf_reference.h" +#include "core/fpdfapi/parser/cpdf_string.h" +#include "core/fpdfdoc/cpdf_dest.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "testing/test_support.h" +#include "third_party/base/ptr_util.h" + +#ifdef PDF_ENABLE_XFA +#include "fpdfsdk/fpdfxfa/cpdfxfa_context.h" +#endif // PDF_ENABLE_XFA + +class CPDF_TestDocument : public CPDF_Document { + public: + CPDF_TestDocument() : CPDF_Document(nullptr) {} + + void SetRoot(CPDF_Dictionary* root) { m_pRootDict = root; } + CPDF_IndirectObjectHolder* GetHolder() { return this; } +}; + +#ifdef PDF_ENABLE_XFA +class CPDF_TestXFAContext : public CPDFXFA_Context { + public: + CPDF_TestXFAContext() + : CPDFXFA_Context(pdfium::MakeUnique()) {} + + void SetRoot(CPDF_Dictionary* root) { + reinterpret_cast(GetPDFDoc())->SetRoot(root); + } + + CPDF_IndirectObjectHolder* GetHolder() { return GetPDFDoc(); } +}; +using CPDF_TestPdfDocument = CPDF_TestXFAContext; +#else // PDF_ENABLE_XFA +using CPDF_TestPdfDocument = CPDF_TestDocument; +#endif // PDF_ENABLE_XFA + +class PDFDocTest : public testing::Test { + public: + struct DictObjInfo { + uint32_t num; + CPDF_Dictionary* obj; + }; + + void SetUp() override { + CPDF_ModuleMgr::Get()->Init(); + + m_pDoc = pdfium::MakeUnique(); + m_pIndirectObjs = m_pDoc->GetHolder(); + + // Setup the root directory. + m_pRootObj = pdfium::MakeUnique(); + m_pDoc->SetRoot(m_pRootObj.get()); + } + + void TearDown() override { + m_pRootObj.reset(); + m_pIndirectObjs = nullptr; + m_pDoc.reset(); + CPDF_ModuleMgr::Destroy(); + } + + std::vector CreateDictObjs(int num) { + std::vector info; + for (int i = 0; i < num; ++i) { + // Objects created will be released by the document. + CPDF_Dictionary* obj = m_pIndirectObjs->NewIndirect(); + info.push_back({obj->GetObjNum(), obj}); + } + return info; + } + + protected: + std::unique_ptr m_pDoc; + UnownedPtr m_pIndirectObjs; + std::unique_ptr m_pRootObj; +}; + +TEST_F(PDFDocTest, FindBookmark) { + { + // No bookmark information. + std::unique_ptr title = + GetFPDFWideString(L""); + EXPECT_EQ(nullptr, FPDFBookmark_Find(m_pDoc.get(), title.get())); + + title = GetFPDFWideString(L"Preface"); + EXPECT_EQ(nullptr, FPDFBookmark_Find(m_pDoc.get(), title.get())); + } + { + // Empty bookmark tree. + m_pRootObj->SetNewFor("Outlines"); + std::unique_ptr title = + GetFPDFWideString(L""); + EXPECT_EQ(nullptr, FPDFBookmark_Find(m_pDoc.get(), title.get())); + + title = GetFPDFWideString(L"Preface"); + EXPECT_EQ(nullptr, FPDFBookmark_Find(m_pDoc.get(), title.get())); + } + { + // Check on a regular bookmark tree. + auto bookmarks = CreateDictObjs(3); + + bookmarks[1].obj->SetNewFor("Title", L"Chapter 1"); + bookmarks[1].obj->SetNewFor("Parent", m_pIndirectObjs.Get(), + bookmarks[0].num); + bookmarks[1].obj->SetNewFor("Next", m_pIndirectObjs.Get(), + bookmarks[2].num); + + bookmarks[2].obj->SetNewFor("Title", L"Chapter 2"); + bookmarks[2].obj->SetNewFor("Parent", m_pIndirectObjs.Get(), + bookmarks[0].num); + bookmarks[2].obj->SetNewFor("Prev", m_pIndirectObjs.Get(), + bookmarks[1].num); + + bookmarks[0].obj->SetNewFor("Type", "Outlines"); + bookmarks[0].obj->SetNewFor("Count", 2); + bookmarks[0].obj->SetNewFor("First", m_pIndirectObjs.Get(), + bookmarks[1].num); + bookmarks[0].obj->SetNewFor("Last", m_pIndirectObjs.Get(), + bookmarks[2].num); + + m_pRootObj->SetNewFor("Outlines", m_pIndirectObjs.Get(), + bookmarks[0].num); + + // Title with no match. + std::unique_ptr title = + GetFPDFWideString(L"Chapter 3"); + EXPECT_EQ(nullptr, FPDFBookmark_Find(m_pDoc.get(), title.get())); + + // Title with partial match only. + title = GetFPDFWideString(L"Chapter"); + EXPECT_EQ(nullptr, FPDFBookmark_Find(m_pDoc.get(), title.get())); + + // Title with a match. + title = GetFPDFWideString(L"Chapter 2"); + EXPECT_EQ(bookmarks[2].obj, FPDFBookmark_Find(m_pDoc.get(), title.get())); + + // Title match is case insensitive. + title = GetFPDFWideString(L"cHaPter 2"); + EXPECT_EQ(bookmarks[2].obj, FPDFBookmark_Find(m_pDoc.get(), title.get())); + } + { + // Circular bookmarks in depth. + auto bookmarks = CreateDictObjs(3); + + bookmarks[1].obj->SetNewFor("Title", L"Chapter 1"); + bookmarks[1].obj->SetNewFor("Parent", m_pIndirectObjs.Get(), + bookmarks[0].num); + bookmarks[1].obj->SetNewFor("First", m_pIndirectObjs.Get(), + bookmarks[2].num); + + bookmarks[2].obj->SetNewFor("Title", L"Chapter 2"); + bookmarks[2].obj->SetNewFor("Parent", m_pIndirectObjs.Get(), + bookmarks[1].num); + bookmarks[2].obj->SetNewFor("First", m_pIndirectObjs.Get(), + bookmarks[1].num); + + bookmarks[0].obj->SetNewFor("Type", "Outlines"); + bookmarks[0].obj->SetNewFor("Count", 2); + bookmarks[0].obj->SetNewFor("First", m_pIndirectObjs.Get(), + bookmarks[1].num); + bookmarks[0].obj->SetNewFor("Last", m_pIndirectObjs.Get(), + bookmarks[2].num); + + m_pRootObj->SetNewFor("Outlines", m_pIndirectObjs.Get(), + bookmarks[0].num); + + // Title with no match. + std::unique_ptr title = + GetFPDFWideString(L"Chapter 3"); + EXPECT_EQ(nullptr, FPDFBookmark_Find(m_pDoc.get(), title.get())); + + // Title with a match. + title = GetFPDFWideString(L"Chapter 2"); + EXPECT_EQ(bookmarks[2].obj, FPDFBookmark_Find(m_pDoc.get(), title.get())); + } + { + // Circular bookmarks in breadth. + auto bookmarks = CreateDictObjs(4); + + bookmarks[1].obj->SetNewFor("Title", L"Chapter 1"); + bookmarks[1].obj->SetNewFor("Parent", m_pIndirectObjs.Get(), + bookmarks[0].num); + bookmarks[1].obj->SetNewFor("Next", m_pIndirectObjs.Get(), + bookmarks[2].num); + + bookmarks[2].obj->SetNewFor("Title", L"Chapter 2"); + bookmarks[2].obj->SetNewFor("Parent", m_pIndirectObjs.Get(), + bookmarks[0].num); + bookmarks[2].obj->SetNewFor("Next", m_pIndirectObjs.Get(), + bookmarks[3].num); + + bookmarks[3].obj->SetNewFor("Title", L"Chapter 3"); + bookmarks[3].obj->SetNewFor("Parent", m_pIndirectObjs.Get(), + bookmarks[0].num); + bookmarks[3].obj->SetNewFor("Next", m_pIndirectObjs.Get(), + bookmarks[1].num); + + bookmarks[0].obj->SetNewFor("Type", "Outlines"); + bookmarks[0].obj->SetNewFor("Count", 2); + bookmarks[0].obj->SetNewFor("First", m_pIndirectObjs.Get(), + bookmarks[1].num); + bookmarks[0].obj->SetNewFor("Last", m_pIndirectObjs.Get(), + bookmarks[2].num); + + m_pRootObj->SetNewFor("Outlines", m_pIndirectObjs.Get(), + bookmarks[0].num); + + // Title with no match. + std::unique_ptr title = + GetFPDFWideString(L"Chapter 8"); + EXPECT_EQ(nullptr, FPDFBookmark_Find(m_pDoc.get(), title.get())); + + // Title with a match. + title = GetFPDFWideString(L"Chapter 3"); + EXPECT_EQ(bookmarks[3].obj, FPDFBookmark_Find(m_pDoc.get(), title.get())); + } +} + +TEST_F(PDFDocTest, GetLocationInPage) { + auto array = pdfium::MakeUnique(); + array->AddNew(0); // Page Index. + array->AddNew("XYZ"); + array->AddNew(4); // X + array->AddNew(5); // Y + array->AddNew(6); // Zoom. + + FPDF_BOOL hasX; + FPDF_BOOL hasY; + FPDF_BOOL hasZoom; + FS_FLOAT x; + FS_FLOAT y; + FS_FLOAT zoom; + + EXPECT_TRUE(FPDFDest_GetLocationInPage(array.get(), &hasX, &hasY, &hasZoom, + &x, &y, &zoom)); + EXPECT_TRUE(hasX); + EXPECT_TRUE(hasY); + EXPECT_TRUE(hasZoom); + EXPECT_EQ(4, x); + EXPECT_EQ(5, y); + EXPECT_EQ(6, zoom); + + array->SetNewAt(2); + array->SetNewAt(3); + array->SetNewAt(4); + EXPECT_TRUE(FPDFDest_GetLocationInPage(array.get(), &hasX, &hasY, &hasZoom, + &x, &y, &zoom)); + EXPECT_FALSE(hasX); + EXPECT_FALSE(hasY); + EXPECT_FALSE(hasZoom); + + array = pdfium::MakeUnique(); + EXPECT_FALSE(FPDFDest_GetLocationInPage(array.get(), &hasX, &hasY, &hasZoom, + &x, &y, &zoom)); +} diff --git a/fpdfsdk/fpdf_edit_embeddertest.cpp b/fpdfsdk/fpdf_edit_embeddertest.cpp new file mode 100644 index 0000000000..074bb40592 --- /dev/null +++ b/fpdfsdk/fpdf_edit_embeddertest.cpp @@ -0,0 +1,1469 @@ +// Copyright 2016 PDFium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include +#include +#include +#include + +#include "core/fpdfapi/font/cpdf_font.h" +#include "core/fpdfapi/page/cpdf_page.h" +#include "core/fpdfapi/parser/cpdf_array.h" +#include "core/fpdfapi/parser/cpdf_dictionary.h" +#include "core/fpdfapi/parser/cpdf_number.h" +#include "core/fpdfapi/parser/cpdf_stream.h" +#include "core/fxcrt/fx_system.h" +#include "fpdfsdk/cpdfsdk_helpers.h" +#include "public/cpp/fpdf_deleters.h" +#include "public/fpdf_annot.h" +#include "public/fpdf_edit.h" +#include "public/fpdfview.h" +#include "testing/embedder_test.h" +#include "testing/gmock/include/gmock/gmock-matchers.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "testing/test_support.h" + +class FPDFEditEmbeddertest : public EmbedderTest { + protected: + FPDF_DOCUMENT CreateNewDocument() { + document_ = FPDF_CreateNewDocument(); + cpdf_doc_ = CPDFDocumentFromFPDFDocument(document_); + return document_; + } + + void CheckFontDescriptor(CPDF_Dictionary* font_dict, + int font_type, + bool bold, + bool italic, + uint32_t size, + const uint8_t* data) { + CPDF_Dictionary* font_desc = font_dict->GetDictFor("FontDescriptor"); + ASSERT_TRUE(font_desc); + EXPECT_EQ("FontDescriptor", font_desc->GetStringFor("Type")); + EXPECT_EQ(font_dict->GetStringFor("BaseFont"), + font_desc->GetStringFor("FontName")); + + // Check that the font descriptor has the required keys according to spec + // 1.7 Table 5.19 + ASSERT_TRUE(font_desc->KeyExist("Flags")); + + int font_flags = font_desc->GetIntegerFor("Flags"); + EXPECT_EQ(bold, FontStyleIsBold(font_flags)); + EXPECT_EQ(italic, FontStyleIsItalic(font_flags)); + EXPECT_TRUE(FontStyleIsNonSymbolic(font_flags)); + ASSERT_TRUE(font_desc->KeyExist("FontBBox")); + + CPDF_Array* fontBBox = font_desc->GetArrayFor("FontBBox"); + ASSERT_TRUE(fontBBox); + EXPECT_EQ(4U, fontBBox->GetCount()); + // Check that the coordinates are in the preferred order according to spec + // 1.7 Section 3.8.4 + EXPECT_TRUE(fontBBox->GetIntegerAt(0) < fontBBox->GetIntegerAt(2)); + EXPECT_TRUE(fontBBox->GetIntegerAt(1) < fontBBox->GetIntegerAt(3)); + + EXPECT_TRUE(font_desc->KeyExist("ItalicAngle")); + EXPECT_TRUE(font_desc->KeyExist("Ascent")); + EXPECT_TRUE(font_desc->KeyExist("Descent")); + EXPECT_TRUE(font_desc->KeyExist("CapHeight")); + EXPECT_TRUE(font_desc->KeyExist("StemV")); + ByteString present("FontFile"); + ByteString absent("FontFile2"); + if (font_type == FPDF_FONT_TRUETYPE) + std::swap(present, absent); + EXPECT_TRUE(font_desc->KeyExist(present)); + EXPECT_FALSE(font_desc->KeyExist(absent)); + + // Check that the font stream is the one that was provided + CPDF_Stream* font_stream = font_desc->GetStreamFor(present); + ASSERT_EQ(size, font_stream->GetRawSize()); + if (font_type == FPDF_FONT_TRUETYPE) { + ASSERT_EQ(static_cast(size), + font_stream->GetDict()->GetIntegerFor("Length1")); + } + uint8_t* stream_data = font_stream->GetRawData(); + for (size_t j = 0; j < size; j++) + EXPECT_EQ(data[j], stream_data[j]) << " at byte " << j; + } + + void CheckCompositeFontWidths(CPDF_Array* widths_array, + CPDF_Font* typed_font) { + // Check that W array is in a format that conforms to PDF spec 1.7 section + // "Glyph Metrics in CIDFonts" (these checks are not + // implementation-specific). + EXPECT_GT(widths_array->GetCount(), 1U); + int num_cids_checked = 0; + int cur_cid = 0; + for (size_t idx = 0; idx < widths_array->GetCount(); idx++) { + int cid = widths_array->GetNumberAt(idx); + EXPECT_GE(cid, cur_cid); + ASSERT_FALSE(++idx == widths_array->GetCount()); + CPDF_Object* next = widths_array->GetObjectAt(idx); + if (next->IsArray()) { + // We are in the c [w1 w2 ...] case + CPDF_Array* arr = next->AsArray(); + int cnt = static_cast(arr->GetCount()); + size_t inner_idx = 0; + for (cur_cid = cid; cur_cid < cid + cnt; cur_cid++) { + uint32_t width = arr->GetNumberAt(inner_idx++); + EXPECT_EQ(width, typed_font->GetCharWidthF(cur_cid)) + << " at cid " << cur_cid; + } + num_cids_checked += cnt; + continue; + } + // Otherwise, are in the c_first c_last w case. + ASSERT_TRUE(next->IsNumber()); + int last_cid = next->AsNumber()->GetInteger(); + ASSERT_FALSE(++idx == widths_array->GetCount()); + uint32_t width = widths_array->GetNumberAt(idx); + for (cur_cid = cid; cur_cid <= last_cid; cur_cid++) { + EXPECT_EQ(width, typed_font->GetCharWidthF(cur_cid)) + << " at cid " << cur_cid; + } + num_cids_checked += last_cid - cid + 1; + } + // Make sure we have a good amount of cids described + EXPECT_GT(num_cids_checked, 900); + } + CPDF_Document* cpdf_doc() { return cpdf_doc_; } + + private: + CPDF_Document* cpdf_doc_; +}; + +namespace { + +const char kExpectedPDF[] = + "%PDF-1.7\r\n" + "%\xA1\xB3\xC5\xD7\r\n" + "1 0 obj\r\n" + "<>\r\n" + "endobj\r\n" + "2 0 obj\r\n" + "<>\r\n" + "endobj\r\n" + "3 0 obj\r\n" + "<>\r\n" + "endobj\r\n" + "4 0 obj\r\n" + "<>>>" + "/Rotate 0/Type/Page" + ">>\r\n" + "endobj\r\n" + "5 0 obj\r\n" + "<>\r\n" + "endobj\r\n" + "xref\r\n" + "0 6\r\n" + "0000000000 65535 f\r\n" + "0000000017 00000 n\r\n" + "0000000066 00000 n\r\n" + "0000000122 00000 n\r\n" + "0000000192 00000 n\r\n" + "0000000311 00000 n\r\n" + "trailer\r\n" + "<<\r\n" + "/Root 1 0 R\r\n" + "/Info 3 0 R\r\n" + "/Size 6/ID\\[<.*><.*>\\]>>\r\n" + "startxref\r\n" + "354\r\n" + "%%EOF\r\n"; + +} // namespace + +TEST_F(FPDFEditEmbeddertest, EmptyCreation) { + EXPECT_TRUE(CreateEmptyDocument()); + FPDF_PAGE page = FPDFPage_New(document(), 0, 640.0, 480.0); + EXPECT_NE(nullptr, page); + // The FPDFPage_GenerateContent call should do nothing. + EXPECT_TRUE(FPDFPage_GenerateContent(page)); + EXPECT_TRUE(FPDF_SaveAsCopy(document(), this, 0)); + + EXPECT_THAT(GetString(), testing::MatchesRegex(std::string( + kExpectedPDF, sizeof(kExpectedPDF)))); + FPDF_ClosePage(page); +} + +// Regression test for https://crbug.com/667012 +TEST_F(FPDFEditEmbeddertest, RasterizePDF) { + const char kAllBlackMd5sum[] = "5708fc5c4a8bd0abde99c8e8f0390615"; + + // Get the bitmap for the original document/ + std::unique_ptr orig_bitmap; + { + EXPECT_TRUE(OpenDocument("black.pdf")); + FPDF_PAGE orig_page = LoadPage(0); + ASSERT_TRUE(orig_page); + orig_bitmap = RenderLoadedPage(orig_page); + CompareBitmap(orig_bitmap.get(), 612, 792, kAllBlackMd5sum); + UnloadPage(orig_page); + } + + // Create a new document from |orig_bitmap| and save it. + { + FPDF_DOCUMENT temp_doc = FPDF_CreateNewDocument(); + FPDF_PAGE temp_page = FPDFPage_New(temp_doc, 0, 612, 792); + + // Add the bitmap to an image object and add the image object to the output + // page. + FPDF_PAGEOBJECT temp_img = FPDFPageObj_NewImageObj(temp_doc); + EXPECT_TRUE( + FPDFImageObj_SetBitmap(&temp_page, 1, temp_img, orig_bitmap.get())); + EXPECT_TRUE(FPDFImageObj_SetMatrix(temp_img, 612, 0, 0, 792, 0, 0)); + FPDFPage_InsertObject(temp_page, temp_img); + EXPECT_TRUE(FPDFPage_GenerateContent(temp_page)); + EXPECT_TRUE(FPDF_SaveAsCopy(temp_doc, this, 0)); + FPDF_ClosePage(temp_page); + FPDF_CloseDocument(temp_doc); + } + + // Get the generated content. Make sure it is at least as big as the original + // PDF. + EXPECT_GT(GetString().size(), 923U); + VerifySavedDocument(612, 792, kAllBlackMd5sum); +} + +TEST_F(FPDFEditEmbeddertest, AddPaths) { + // Start with a blank page + FPDF_PAGE page = FPDFPage_New(CreateNewDocument(), 0, 612, 792); + ASSERT_TRUE(page); + + // We will first add a red rectangle + FPDF_PAGEOBJECT red_rect = FPDFPageObj_CreateNewRect(10, 10, 20, 20); + ASSERT_TRUE(red_rect); + // Expect false when trying to set colors out of range + EXPECT_FALSE(FPDFPath_SetStrokeColor(red_rect, 100, 100, 100, 300)); + EXPECT_FALSE(FPDFPath_SetFillColor(red_rect, 200, 256, 200, 0)); + + // Fill rectangle with red and insert to the page + EXPECT_TRUE(FPDFPath_SetFillColor(red_rect, 255, 0, 0, 255)); + EXPECT_TRUE(FPDFPath_SetDrawMode(red_rect, FPDF_FILLMODE_ALTERNATE, 0)); + FPDFPage_InsertObject(page, red_rect); + { + std::unique_ptr page_bitmap = + RenderPageWithFlags(page, nullptr, 0); + CompareBitmap(page_bitmap.get(), 612, 792, + "66d02eaa6181e2c069ce2ea99beda497"); + } + + // Now add to that a green rectangle with some medium alpha + FPDF_PAGEOBJECT green_rect = FPDFPageObj_CreateNewRect(100, 100, 40, 40); + EXPECT_TRUE(FPDFPath_SetFillColor(green_rect, 0, 255, 0, 128)); + + // Make sure the type of the rectangle is a path. + EXPECT_EQ(FPDF_PAGEOBJ_PATH, FPDFPageObj_GetType(green_rect)); + + // Make sure we get back the same color we set previously. + unsigned int R; + unsigned int G; + unsigned int B; + unsigned int A; + EXPECT_TRUE(FPDFPath_GetFillColor(green_rect, &R, &G, &B, &A)); + EXPECT_EQ(0U, R); + EXPECT_EQ(255U, G); + EXPECT_EQ(0U, B); + EXPECT_EQ(128U, A); + + // Make sure the path has 5 points (1 FXPT_TYPE::MoveTo and 4 + // FXPT_TYPE::LineTo). + ASSERT_EQ(5, FPDFPath_CountSegments(green_rect)); + // Verify actual coordinates. + FPDF_PATHSEGMENT segment = FPDFPath_GetPathSegment(green_rect, 0); + float x; + float y; + EXPECT_TRUE(FPDFPathSegment_GetPoint(segment, &x, &y)); + EXPECT_EQ(100, x); + EXPECT_EQ(100, y); + EXPECT_EQ(FPDF_SEGMENT_MOVETO, FPDFPathSegment_GetType(segment)); + EXPECT_FALSE(FPDFPathSegment_GetClose(segment)); + segment = FPDFPath_GetPathSegment(green_rect, 1); + EXPECT_TRUE(FPDFPathSegment_GetPoint(segment, &x, &y)); + EXPECT_EQ(100, x); + EXPECT_EQ(140, y); + EXPECT_EQ(FPDF_SEGMENT_LINETO, FPDFPathSegment_GetType(segment)); + EXPECT_FALSE(FPDFPathSegment_GetClose(segment)); + segment = FPDFPath_GetPathSegment(green_rect, 2); + EXPECT_TRUE(FPDFPathSegment_GetPoint(segment, &x, &y)); + EXPECT_EQ(140, x); + EXPECT_EQ(140, y); + EXPECT_EQ(FPDF_SEGMENT_LINETO, FPDFPathSegment_GetType(segment)); + EXPECT_FALSE(FPDFPathSegment_GetClose(segment)); + segment = FPDFPath_GetPathSegment(green_rect, 3); + EXPECT_TRUE(FPDFPathSegment_GetPoint(segment, &x, &y)); + EXPECT_EQ(140, x); + EXPECT_EQ(100, y); + EXPECT_EQ(FPDF_SEGMENT_LINETO, FPDFPathSegment_GetType(segment)); + EXPECT_FALSE(FPDFPathSegment_GetClose(segment)); + segment = FPDFPath_GetPathSegment(green_rect, 4); + EXPECT_TRUE(FPDFPathSegment_GetPoint(segment, &x, &y)); + EXPECT_EQ(100, x); + EXPECT_EQ(100, y); + EXPECT_EQ(FPDF_SEGMENT_LINETO, FPDFPathSegment_GetType(segment)); + EXPECT_TRUE(FPDFPathSegment_GetClose(segment)); + + EXPECT_TRUE(FPDFPath_SetDrawMode(green_rect, FPDF_FILLMODE_WINDING, 0)); + FPDFPage_InsertObject(page, green_rect); + { + std::unique_ptr page_bitmap = + RenderPageWithFlags(page, nullptr, 0); + CompareBitmap(page_bitmap.get(), 612, 792, + "7b0b87604594e773add528fae567a558"); + } + + // Add a black triangle. + FPDF_PAGEOBJECT black_path = FPDFPageObj_CreateNewPath(400, 100); + EXPECT_TRUE(FPDFPath_SetFillColor(black_path, 0, 0, 0, 200)); + EXPECT_TRUE(FPDFPath_SetDrawMode(black_path, FPDF_FILLMODE_ALTERNATE, 0)); + EXPECT_TRUE(FPDFPath_LineTo(black_path, 400, 200)); + EXPECT_TRUE(FPDFPath_LineTo(black_path, 300, 100)); + EXPECT_TRUE(FPDFPath_Close(black_path)); + + // Make sure the path has 3 points (1 FXPT_TYPE::MoveTo and 2 + // FXPT_TYPE::LineTo). + ASSERT_EQ(3, FPDFPath_CountSegments(black_path)); + // Verify actual coordinates. + segment = FPDFPath_GetPathSegment(black_path, 0); + EXPECT_TRUE(FPDFPathSegment_GetPoint(segment, &x, &y)); + EXPECT_EQ(400, x); + EXPECT_EQ(100, y); + EXPECT_EQ(FPDF_SEGMENT_MOVETO, FPDFPathSegment_GetType(segment)); + EXPECT_FALSE(FPDFPathSegment_GetClose(segment)); + segment = FPDFPath_GetPathSegment(black_path, 1); + EXPECT_TRUE(FPDFPathSegment_GetPoint(segment, &x, &y)); + EXPECT_EQ(400, x); + EXPECT_EQ(200, y); + EXPECT_EQ(FPDF_SEGMENT_LINETO, FPDFPathSegment_GetType(segment)); + EXPECT_FALSE(FPDFPathSegment_GetClose(segment)); + segment = FPDFPath_GetPathSegment(black_path, 2); + EXPECT_TRUE(FPDFPathSegment_GetPoint(segment, &x, &y)); + EXPECT_EQ(300, x); + EXPECT_EQ(100, y); + EXPECT_EQ(FPDF_SEGMENT_LINETO, FPDFPathSegment_GetType(segment)); + EXPECT_TRUE(FPDFPathSegment_GetClose(segment)); + // Make sure out of bounds index access fails properly. + EXPECT_EQ(nullptr, FPDFPath_GetPathSegment(black_path, 3)); + + FPDFPage_InsertObject(page, black_path); + { + std::unique_ptr page_bitmap = + RenderPageWithFlags(page, nullptr, 0); + CompareBitmap(page_bitmap.get(), 612, 792, + "eadc8020a14dfcf091da2688733d8806"); + } + + // Now add a more complex blue path. + FPDF_PAGEOBJECT blue_path = FPDFPageObj_CreateNewPath(200, 200); + EXPECT_TRUE(FPDFPath_SetFillColor(blue_path, 0, 0, 255, 255)); + EXPECT_TRUE(FPDFPath_SetDrawMode(blue_path, FPDF_FILLMODE_WINDING, 0)); + EXPECT_TRUE(FPDFPath_LineTo(blue_path, 230, 230)); + EXPECT_TRUE(FPDFPath_BezierTo(blue_path, 250, 250, 280, 280, 300, 300)); + EXPECT_TRUE(FPDFPath_LineTo(blue_path, 325, 325)); + EXPECT_TRUE(FPDFPath_LineTo(blue_path, 350, 325)); + EXPECT_TRUE(FPDFPath_BezierTo(blue_path, 375, 330, 390, 360, 400, 400)); + EXPECT_TRUE(FPDFPath_Close(blue_path)); + FPDFPage_InsertObject(page, blue_path); + const char kLastMD5[] = "9823e1a21bd9b72b6a442ba4f12af946"; + { + std::unique_ptr page_bitmap = + RenderPageWithFlags(page, nullptr, 0); + CompareBitmap(page_bitmap.get(), 612, 792, kLastMD5); + } + + // Now save the result, closing the page and document + EXPECT_TRUE(FPDFPage_GenerateContent(page)); + EXPECT_TRUE(FPDF_SaveAsCopy(document(), this, 0)); + FPDF_ClosePage(page); + + // Render the saved result + VerifySavedDocument(612, 792, kLastMD5); +} + +TEST_F(FPDFEditEmbeddertest, RemovePageObject) { + // Load document with some text. + EXPECT_TRUE(OpenDocument("hello_world.pdf")); + FPDF_PAGE page = LoadPage(0); + ASSERT_TRUE(page); + + // Show what the original file looks like. + { +#if _FX_PLATFORM_ == _FX_PLATFORM_APPLE_ + const char kOriginalMD5[] = "b90475ca64d1348c3bf5e2b77ad9187a"; +#elif _FX_PLATFORM_ == _FX_PLATFORM_WINDOWS_ + const char kOriginalMD5[] = "e5a6fa28298db07484cd922f3e210c88"; +#else + const char kOriginalMD5[] = "2baa4c0e1758deba1b9c908e1fbd04ed"; +#endif + std::unique_ptr page_bitmap = + RenderPageWithFlags(page, nullptr, 0); + CompareBitmap(page_bitmap.get(), 200, 200, kOriginalMD5); + } + + // Get the "Hello, world!" text object and remove it. + ASSERT_EQ(2, FPDFPage_CountObjects(page)); + FPDF_PAGEOBJECT page_object = FPDFPage_GetObject(page, 0); + ASSERT_TRUE(page_object); + EXPECT_TRUE(FPDFPage_RemoveObject(page, page_object)); + + // Verify the "Hello, world!" text is gone. + { +#if _FX_PLATFORM_ == _FX_PLATFORM_APPLE_ + const char kRemovedMD5[] = "af760c4702467cb1492a57fb8215efaa"; +#elif _FX_PLATFORM_ == _FX_PLATFORM_WINDOWS_ + const char kRemovedMD5[] = "72be917349bf7004a5c39661fe1fc433"; +#else + const char kRemovedMD5[] = "b76df015fe88009c3c342395df96abf1"; +#endif + std::unique_ptr page_bitmap = + RenderPageWithFlags(page, nullptr, 0); + CompareBitmap(page_bitmap.get(), 200, 200, kRemovedMD5); + } + ASSERT_EQ(1, FPDFPage_CountObjects(page)); + + UnloadPage(page); + FPDFPageObj_Destroy(page_object); +} + +TEST_F(FPDFEditEmbeddertest, RemoveMarkedObjectsPrime) { + // Load document with some text. + EXPECT_TRUE(OpenDocument("text_in_page_marked.pdf")); + FPDF_PAGE page = LoadPage(0); + ASSERT_TRUE(page); + + // Show what the original file looks like. + { +#if _FX_PLATFORM_ == _FX_PLATFORM_APPLE_ + const char kOriginalMD5[] = "5a5eb63cb21cc15084fea1f14284b8df"; +#elif _FX_PLATFORM_ == _FX_PLATFORM_WINDOWS_ + const char kOriginalMD5[] = "587c507a40f613f9c530b2ce2d58d655"; +#else + const char kOriginalMD5[] = "2edc6e70d54889aa0c0b7bdf3e168f86"; +#endif + std::unique_ptr page_bitmap = + RenderPageWithFlags(page, nullptr, 0); + CompareBitmap(page_bitmap.get(), 200, 200, kOriginalMD5); + } + + // Iterate over all objects, counting the number of times each content mark + // name appears. + int object_count = FPDFPage_CountObjects(page); + ASSERT_EQ(19, object_count); + + unsigned long prime_count = 0; + unsigned long square_count = 0; + unsigned long greater_than_ten_count = 0; + std::vector primes; + for (int i = 0; i < object_count; ++i) { + FPDF_PAGEOBJECT page_object = FPDFPage_GetObject(page, i); + + int mark_count = FPDFPageObj_CountMarks(page_object); + for (int j = 0; j < mark_count; ++j) { + FPDF_PAGEOBJECTMARK mark = FPDFPageObj_GetMark(page_object, j); + + char buffer[256]; + ASSERT_GT(FPDFPageObjMark_GetName(mark, buffer, 256), 0u); + std::wstring name = + GetPlatformWString(reinterpret_cast(buffer)); + if (name == L"Prime") { + prime_count++; + primes.push_back(page_object); + } else if (name == L"Square") { + square_count++; + } else if (name == L"GreaterThanTen") { + greater_than_ten_count++; + } else { + FAIL(); + } + } + } + + // Expect certain number of tagged objects. The test file contains strings + // from 1 to 19. + EXPECT_EQ(8u, prime_count); + EXPECT_EQ(4u, square_count); + EXPECT_EQ(9u, greater_than_ten_count); + + // Remove all objects marked with "Prime". + for (FPDF_PAGEOBJECT page_object : primes) { + EXPECT_TRUE(FPDFPage_RemoveObject(page, page_object)); + FPDFPageObj_Destroy(page_object); + } + + EXPECT_EQ(11, FPDFPage_CountObjects(page)); + + { +#if _FX_PLATFORM_ == _FX_PLATFORM_APPLE_ + const char kNonPrimesMD5[] = "57e76dc7375d896704f0fd6d6d1b9e65"; +#elif _FX_PLATFORM_ == _FX_PLATFORM_WINDOWS_ + const char kNonPrimesMD5[] = "4d906b57fba36c70c600cf50d60f508c"; +#else + const char kNonPrimesMD5[] = "33d9c45bec41ead92a295e252f6b7922"; +#endif + std::unique_ptr page_bitmap = + RenderPageWithFlags(page, nullptr, 0); + CompareBitmap(page_bitmap.get(), 200, 200, kNonPrimesMD5); + } + + UnloadPage(page); +} + +TEST_F(FPDFEditEmbeddertest, AddAndRemovePaths) { + // Start with a blank page. + FPDF_PAGE page = FPDFPage_New(CreateNewDocument(), 0, 612, 792); + ASSERT_TRUE(page); + + // Render the blank page and verify it's a blank bitmap. + const char kBlankMD5[] = "1940568c9ba33bac5d0b1ee9558c76b3"; + { + std::unique_ptr page_bitmap = + RenderPageWithFlags(page, nullptr, 0); + CompareBitmap(page_bitmap.get(), 612, 792, kBlankMD5); + } + ASSERT_EQ(0, FPDFPage_CountObjects(page)); + + // Add a red rectangle. + FPDF_PAGEOBJECT red_rect = FPDFPageObj_CreateNewRect(10, 10, 20, 20); + ASSERT_TRUE(red_rect); + EXPECT_TRUE(FPDFPath_SetFillColor(red_rect, 255, 0, 0, 255)); + EXPECT_TRUE(FPDFPath_SetDrawMode(red_rect, FPDF_FILLMODE_ALTERNATE, 0)); + FPDFPage_InsertObject(page, red_rect); + const char kRedRectangleMD5[] = "66d02eaa6181e2c069ce2ea99beda497"; + { + std::unique_ptr page_bitmap = + RenderPageWithFlags(page, nullptr, 0); + CompareBitmap(page_bitmap.get(), 612, 792, kRedRectangleMD5); + } + EXPECT_EQ(1, FPDFPage_CountObjects(page)); + + // Remove rectangle and verify it does not render anymore and the bitmap is + // back to a blank one. + EXPECT_TRUE(FPDFPage_RemoveObject(page, red_rect)); + { + std::unique_ptr page_bitmap = + RenderPageWithFlags(page, nullptr, 0); + CompareBitmap(page_bitmap.get(), 612, 792, kBlankMD5); + } + EXPECT_EQ(0, FPDFPage_CountObjects(page)); + + // Trying to remove an object not in the page should return false. + EXPECT_FALSE(FPDFPage_RemoveObject(page, red_rect)); + + FPDF_ClosePage(page); + FPDFPageObj_Destroy(red_rect); +} + +TEST_F(FPDFEditEmbeddertest, PathsPoints) { + CreateNewDocument(); + FPDF_PAGEOBJECT img = FPDFPageObj_NewImageObj(document_); + // This should fail gracefully, even if img is not a path. + ASSERT_EQ(-1, FPDFPath_CountSegments(img)); + + // This should fail gracefully, even if path is NULL. + ASSERT_EQ(-1, FPDFPath_CountSegments(nullptr)); + + // FPDFPath_GetPathSegment() with a non-path. + ASSERT_EQ(nullptr, FPDFPath_GetPathSegment(img, 0)); + // FPDFPath_GetPathSegment() with a NULL path. + ASSERT_EQ(nullptr, FPDFPath_GetPathSegment(nullptr, 0)); + float x; + float y; + // FPDFPathSegment_GetPoint() with a NULL segment. + EXPECT_FALSE(FPDFPathSegment_GetPoint(nullptr, &x, &y)); + + // FPDFPathSegment_GetType() with a NULL segment. + ASSERT_EQ(FPDF_SEGMENT_UNKNOWN, FPDFPathSegment_GetType(nullptr)); + + // FPDFPathSegment_GetClose() with a NULL segment. + EXPECT_FALSE(FPDFPathSegment_GetClose(nullptr)); + + FPDFPageObj_Destroy(img); +} + +TEST_F(FPDFEditEmbeddertest, PathOnTopOfText) { + // Load document with some text + EXPECT_TRUE(OpenDocument("hello_world.pdf")); + FPDF_PAGE page = LoadPage(0); + ASSERT_TRUE(page); + + // Add an opaque rectangle on top of some of the text. + FPDF_PAGEOBJECT red_rect = FPDFPageObj_CreateNewRect(20, 100, 50, 50); + EXPECT_TRUE(FPDFPath_SetFillColor(red_rect, 255, 0, 0, 255)); + EXPECT_TRUE(FPDFPath_SetDrawMode(red_rect, FPDF_FILLMODE_ALTERNATE, 0)); + FPDFPage_InsertObject(page, red_rect); + + // Add a transparent triangle on top of other part of the text. + FPDF_PAGEOBJECT black_path = FPDFPageObj_CreateNewPath(20, 50); + EXPECT_TRUE(FPDFPath_SetFillColor(black_path, 0, 0, 0, 100)); + EXPECT_TRUE(FPDFPath_SetDrawMode(black_path, FPDF_FILLMODE_ALTERNATE, 0)); + EXPECT_TRUE(FPDFPath_LineTo(black_path, 30, 80)); + EXPECT_TRUE(FPDFPath_LineTo(black_path, 40, 10)); + EXPECT_TRUE(FPDFPath_Close(black_path)); + FPDFPage_InsertObject(page, black_path); + + // Render and check the result. Text is slightly different on Mac. + std::unique_ptr bitmap = RenderLoadedPage(page); +#if _FX_PLATFORM_ == _FX_PLATFORM_APPLE_ + const char md5[] = "f9e6fa74230f234286bfcada9f7606d8"; +#else + const char md5[] = "aa71b09b93b55f467f1290e5111babee"; +#endif + CompareBitmap(bitmap.get(), 200, 200, md5); + UnloadPage(page); +} + +TEST_F(FPDFEditEmbeddertest, EditOverExistingContent) { + // Load document with existing content + EXPECT_TRUE(OpenDocument("bug_717.pdf")); + FPDF_PAGE page = LoadPage(0); + ASSERT_TRUE(page); + + // Add a transparent rectangle on top of the existing content + FPDF_PAGEOBJECT red_rect2 = FPDFPageObj_CreateNewRect(90, 700, 25, 50); + EXPECT_TRUE(FPDFPath_SetFillColor(red_rect2, 255, 0, 0, 100)); + EXPECT_TRUE(FPDFPath_SetDrawMode(red_rect2, FPDF_FILLMODE_ALTERNATE, 0)); + FPDFPage_InsertObject(page, red_rect2); + + // Add an opaque rectangle on top of the existing content + FPDF_PAGEOBJECT red_rect = FPDFPageObj_CreateNewRect(115, 700, 25, 50); + EXPECT_TRUE(FPDFPath_SetFillColor(red_rect, 255, 0, 0, 255)); + EXPECT_TRUE(FPDFPath_SetDrawMode(red_rect, FPDF_FILLMODE_ALTERNATE, 0)); + FPDFPage_InsertObject(page, red_rect); + + std::unique_ptr bitmap = RenderLoadedPage(page); + CompareBitmap(bitmap.get(), 612, 792, "ad04e5bd0f471a9a564fb034bd0fb073"); + EXPECT_TRUE(FPDFPage_GenerateContent(page)); + + // Now save the result, closing the page and document + EXPECT_TRUE(FPDF_SaveAsCopy(document(), this, 0)); + UnloadPage(page); + + OpenSavedDocument(); + FPDF_PAGE saved_page = LoadSavedPage(0); + VerifySavedRendering(saved_page, 612, 792, + "ad04e5bd0f471a9a564fb034bd0fb073"); + + ClearString(); + // Add another opaque rectangle on top of the existing content + FPDF_PAGEOBJECT green_rect = FPDFPageObj_CreateNewRect(150, 700, 25, 50); + EXPECT_TRUE(FPDFPath_SetFillColor(green_rect, 0, 255, 0, 255)); + EXPECT_TRUE(FPDFPath_SetDrawMode(green_rect, FPDF_FILLMODE_ALTERNATE, 0)); + FPDFPage_InsertObject(saved_page, green_rect); + + // Add another transparent rectangle on top of existing content + FPDF_PAGEOBJECT green_rect2 = FPDFPageObj_CreateNewRect(175, 700, 25, 50); + EXPECT_TRUE(FPDFPath_SetFillColor(green_rect2, 0, 255, 0, 100)); + EXPECT_TRUE(FPDFPath_SetDrawMode(green_rect2, FPDF_FILLMODE_ALTERNATE, 0)); + FPDFPage_InsertObject(saved_page, green_rect2); + const char kLastMD5[] = "4b5b00f824620f8c9b8801ebb98e1cdd"; + { + std::unique_ptr new_bitmap = + RenderSavedPage(saved_page); + CompareBitmap(new_bitmap.get(), 612, 792, kLastMD5); + } + EXPECT_TRUE(FPDFPage_GenerateContent(saved_page)); + + // Now save the result, closing the page and document + EXPECT_TRUE(FPDF_SaveAsCopy(saved_document_, this, 0)); + + CloseSavedPage(saved_page); + CloseSavedDocument(); + + // Render the saved result + VerifySavedDocument(612, 792, kLastMD5); +} + +TEST_F(FPDFEditEmbeddertest, AddStrokedPaths) { + // Start with a blank page + FPDF_PAGE page = FPDFPage_New(CreateNewDocument(), 0, 612, 792); + + // Add a large stroked rectangle (fill color should not affect it). + FPDF_PAGEOBJECT rect = FPDFPageObj_CreateNewRect(20, 20, 200, 400); + EXPECT_TRUE(FPDFPath_SetFillColor(rect, 255, 0, 0, 255)); + EXPECT_TRUE(FPDFPath_SetStrokeColor(rect, 0, 255, 0, 255)); + EXPECT_TRUE(FPDFPath_SetStrokeWidth(rect, 15.0f)); + EXPECT_TRUE(FPDFPath_SetDrawMode(rect, 0, 1)); + FPDFPage_InsertObject(page, rect); + { + std::unique_ptr page_bitmap = + RenderPageWithFlags(page, nullptr, 0); + CompareBitmap(page_bitmap.get(), 612, 792, + "64bd31f862a89e0a9e505a5af6efd506"); + } + + // Add crossed-checkmark + FPDF_PAGEOBJECT check = FPDFPageObj_CreateNewPath(300, 500); + EXPECT_TRUE(FPDFPath_LineTo(check, 400, 400)); + EXPECT_TRUE(FPDFPath_LineTo(check, 600, 600)); + EXPECT_TRUE(FPDFPath_MoveTo(check, 400, 600)); + EXPECT_TRUE(FPDFPath_LineTo(check, 600, 400)); + EXPECT_TRUE(FPDFPath_SetStrokeColor(check, 128, 128, 128, 180)); + EXPECT_TRUE(FPDFPath_SetStrokeWidth(check, 8.35f)); + EXPECT_TRUE(FPDFPath_SetDrawMode(check, 0, 1)); + FPDFPage_InsertObject(page, check); + { + std::unique_ptr page_bitmap = + RenderPageWithFlags(page, nullptr, 0); + CompareBitmap(page_bitmap.get(), 612, 792, + "4b6f3b9d25c4e194821217d5016c3724"); + } + + // Add stroked and filled oval-ish path. + FPDF_PAGEOBJECT path = FPDFPageObj_CreateNewPath(250, 100); + EXPECT_TRUE(FPDFPath_BezierTo(path, 180, 166, 180, 233, 250, 300)); + EXPECT_TRUE(FPDFPath_LineTo(path, 255, 305)); + EXPECT_TRUE(FPDFPath_BezierTo(path, 325, 233, 325, 166, 255, 105)); + EXPECT_TRUE(FPDFPath_Close(path)); + EXPECT_TRUE(FPDFPath_SetFillColor(path, 200, 128, 128, 100)); + EXPECT_TRUE(FPDFPath_SetStrokeColor(path, 128, 200, 128, 150)); + EXPECT_TRUE(FPDFPath_SetStrokeWidth(path, 10.5f)); + EXPECT_TRUE(FPDFPath_SetDrawMode(path, FPDF_FILLMODE_ALTERNATE, 1)); + FPDFPage_InsertObject(page, path); + { + std::unique_ptr page_bitmap = + RenderPageWithFlags(page, nullptr, 0); + CompareBitmap(page_bitmap.get(), 612, 792, + "ff3e6a22326754944cc6e56609acd73b"); + } + FPDF_ClosePage(page); +} + +TEST_F(FPDFEditEmbeddertest, AddStandardFontText) { + // Start with a blank page + FPDF_PAGE page = FPDFPage_New(CreateNewDocument(), 0, 612, 792); + + // Add some text to the page + FPDF_PAGEOBJECT text_object1 = + FPDFPageObj_NewTextObj(document(), "Arial", 12.0f); + EXPECT_TRUE(text_object1); + std::unique_ptr text1 = + GetFPDFWideString(L"I'm at the bottom of the page"); + EXPECT_TRUE(FPDFText_SetText(text_object1, text1.get())); + FPDFPageObj_Transform(text_object1, 1, 0, 0, 1, 20, 20); + FPDFPage_InsertObject(page, text_object1); + { + std::unique_ptr page_bitmap = + RenderPageWithFlags(page, nullptr, 0); +#if _FX_PLATFORM_ == _FX_PLATFORM_APPLE_ + const char md5[] = "a4dddc1a3930fa694bbff9789dab4161"; +#else + const char md5[] = "eacaa24573b8ce997b3882595f096f00"; +#endif + CompareBitmap(page_bitmap.get(), 612, 792, md5); + } + + // Try another font + FPDF_PAGEOBJECT text_object2 = + FPDFPageObj_NewTextObj(document(), "TimesNewRomanBold", 15.0f); + EXPECT_TRUE(text_object2); + std::unique_ptr text2 = + GetFPDFWideString(L"Hi, I'm Bold. Times New Roman Bold."); + EXPECT_TRUE(FPDFText_SetText(text_object2, text2.get())); + FPDFPageObj_Transform(text_object2, 1, 0, 0, 1, 100, 600); + FPDFPage_InsertObject(page, text_object2); + { + std::unique_ptr page_bitmap = + RenderPageWithFlags(page, nullptr, 0); +#if _FX_PLATFORM_ == _FX_PLATFORM_APPLE_ + const char md5_2[] = "a5c4ace4c6f27644094813fe1441a21c"; +#elif _FX_PLATFORM_ == _FX_PLATFORM_WINDOWS_ + const char md5_2[] = "2587eac9a787e97a37636d54d11bd28d"; +#else + const char md5_2[] = "76fcc7d08aa15445efd2e2ceb7c6cc3b"; +#endif + CompareBitmap(page_bitmap.get(), 612, 792, md5_2); + } + + // And some randomly transformed text + FPDF_PAGEOBJECT text_object3 = + FPDFPageObj_NewTextObj(document(), "Courier-Bold", 20.0f); + EXPECT_TRUE(text_object3); + std::unique_ptr text3 = + GetFPDFWideString(L"Can you read me? <:)>"); + EXPECT_TRUE(FPDFText_SetText(text_object3, text3.get())); + FPDFPageObj_Transform(text_object3, 1, 1.5, 2, 0.5, 200, 200); + FPDFPage_InsertObject(page, text_object3); + { + std::unique_ptr page_bitmap = + RenderPageWithFlags(page, nullptr, 0); +#if _FX_PLATFORM_ == _FX_PLATFORM_APPLE_ + const char md5_3[] = "40b3ef04f915ff4c4208948001763544"; +#elif _FX_PLATFORM_ == _FX_PLATFORM_WINDOWS_ + const char md5_3[] = "7cb61ec112cf400b489360d443ffc9d2"; +#else + const char md5_3[] = "b8a21668f1dab625af7c072e07fcefc4"; +#endif + CompareBitmap(page_bitmap.get(), 612, 792, md5_3); + } + + // TODO(npm): Why are there issues with text rotated by 90 degrees? + // TODO(npm): FPDF_SaveAsCopy not giving the desired result after this. + FPDF_ClosePage(page); +} + +TEST_F(FPDFEditEmbeddertest, GraphicsData) { + // New page + std::unique_ptr page( + FPDFPage_New(CreateNewDocument(), 0, 612, 792)); + + // Create a rect with nontrivial graphics + FPDF_PAGEOBJECT rect1 = FPDFPageObj_CreateNewRect(10, 10, 100, 100); + FPDFPageObj_SetBlendMode(rect1, "Color"); + FPDFPage_InsertObject(page.get(), rect1); + EXPECT_TRUE(FPDFPage_GenerateContent(page.get())); + + // Check that the ExtGState was created + CPDF_Page* cpage = CPDFPageFromFPDFPage(page.get()); + CPDF_Dictionary* graphics_dict = cpage->m_pResources->GetDictFor("ExtGState"); + ASSERT_TRUE(graphics_dict); + EXPECT_EQ(2, static_cast(graphics_dict->GetCount())); + + // Add a text object causing no change to the graphics dictionary + FPDF_PAGEOBJECT text1 = FPDFPageObj_NewTextObj(document(), "Arial", 12.0f); + // Only alpha, the last component, matters for the graphics dictionary. And + // the default value is 255. + EXPECT_TRUE(FPDFText_SetFillColor(text1, 100, 100, 100, 255)); + FPDFPage_InsertObject(page.get(), text1); + EXPECT_TRUE(FPDFPage_GenerateContent(page.get())); + EXPECT_EQ(2, static_cast(graphics_dict->GetCount())); + + // Add a text object increasing the size of the graphics dictionary + FPDF_PAGEOBJECT text2 = + FPDFPageObj_NewTextObj(document(), "Times-Roman", 12.0f); + FPDFPage_InsertObject(page.get(), text2); + FPDFPageObj_SetBlendMode(text2, "Darken"); + EXPECT_TRUE(FPDFText_SetFillColor(text2, 0, 0, 255, 150)); + EXPECT_TRUE(FPDFPage_GenerateContent(page.get())); + EXPECT_EQ(3, static_cast(graphics_dict->GetCount())); + + // Add a path that should reuse graphics + FPDF_PAGEOBJECT path = FPDFPageObj_CreateNewPath(400, 100); + FPDFPageObj_SetBlendMode(path, "Darken"); + EXPECT_TRUE(FPDFPath_SetFillColor(path, 200, 200, 100, 150)); + FPDFPage_InsertObject(page.get(), path); + EXPECT_TRUE(FPDFPage_GenerateContent(page.get())); + EXPECT_EQ(3, static_cast(graphics_dict->GetCount())); + + // Add a rect increasing the size of the graphics dictionary + FPDF_PAGEOBJECT rect2 = FPDFPageObj_CreateNewRect(10, 10, 100, 100); + FPDFPageObj_SetBlendMode(rect2, "Darken"); + EXPECT_TRUE(FPDFPath_SetFillColor(rect2, 0, 0, 255, 150)); + EXPECT_TRUE(FPDFPath_SetStrokeColor(rect2, 0, 0, 0, 200)); + FPDFPage_InsertObject(page.get(), rect2); + EXPECT_TRUE(FPDFPage_GenerateContent(page.get())); + EXPECT_EQ(4, static_cast(graphics_dict->GetCount())); +} + +TEST_F(FPDFEditEmbeddertest, DoubleGenerating) { + // Start with a blank page + FPDF_PAGE page = FPDFPage_New(CreateNewDocument(), 0, 612, 792); + + // Add a red rectangle with some non-default alpha + FPDF_PAGEOBJECT rect = FPDFPageObj_CreateNewRect(10, 10, 100, 100); + EXPECT_TRUE(FPDFPath_SetFillColor(rect, 255, 0, 0, 128)); + EXPECT_TRUE(FPDFPath_SetDrawMode(rect, FPDF_FILLMODE_WINDING, 0)); + FPDFPage_InsertObject(page, rect); + EXPECT_TRUE(FPDFPage_GenerateContent(page)); + + // Check the ExtGState + CPDF_Page* cpage = CPDFPageFromFPDFPage(page); + CPDF_Dictionary* graphics_dict = cpage->m_pResources->GetDictFor("ExtGState"); + ASSERT_TRUE(graphics_dict); + EXPECT_EQ(2, static_cast(graphics_dict->GetCount())); + + // Check the bitmap + { + std::unique_ptr page_bitmap = + RenderPageWithFlags(page, nullptr, 0); + CompareBitmap(page_bitmap.get(), 612, 792, + "5384da3406d62360ffb5cac4476fff1c"); + } + + // Never mind, my new favorite color is blue, increase alpha + EXPECT_TRUE(FPDFPath_SetFillColor(rect, 0, 0, 255, 180)); + EXPECT_TRUE(FPDFPage_GenerateContent(page)); + EXPECT_EQ(3, static_cast(graphics_dict->GetCount())); + + // Check that bitmap displays changed content + { + std::unique_ptr page_bitmap = + RenderPageWithFlags(page, nullptr, 0); + CompareBitmap(page_bitmap.get(), 612, 792, + "2e51656f5073b0bee611d9cd086aa09c"); + } + + // And now generate, without changes + EXPECT_TRUE(FPDFPage_GenerateContent(page)); + EXPECT_EQ(3, static_cast(graphics_dict->GetCount())); + { + std::unique_ptr page_bitmap = + RenderPageWithFlags(page, nullptr, 0); + CompareBitmap(page_bitmap.get(), 612, 792, + "2e51656f5073b0bee611d9cd086aa09c"); + } + + // Add some text to the page + FPDF_PAGEOBJECT text_object = + FPDFPageObj_NewTextObj(document(), "Arial", 12.0f); + std::unique_ptr text = + GetFPDFWideString(L"Something something #text# something"); + EXPECT_TRUE(FPDFText_SetText(text_object, text.get())); + FPDFPageObj_Transform(text_object, 1, 0, 0, 1, 300, 300); + FPDFPage_InsertObject(page, text_object); + EXPECT_TRUE(FPDFPage_GenerateContent(page)); + CPDF_Dictionary* font_dict = cpage->m_pResources->GetDictFor("Font"); + ASSERT_TRUE(font_dict); + EXPECT_EQ(1, static_cast(font_dict->GetCount())); + + // Generate yet again, check dicts are reasonably sized + EXPECT_TRUE(FPDFPage_GenerateContent(page)); + EXPECT_EQ(3, static_cast(graphics_dict->GetCount())); + EXPECT_EQ(1, static_cast(font_dict->GetCount())); + FPDF_ClosePage(page); +} + +TEST_F(FPDFEditEmbeddertest, LoadSimpleType1Font) { + CreateNewDocument(); + // TODO(npm): use other fonts after disallowing loading any font as any type + const CPDF_Font* stock_font = + CPDF_Font::GetStockFont(cpdf_doc(), "Times-Bold"); + const uint8_t* data = stock_font->GetFont()->GetFontData(); + const uint32_t size = stock_font->GetFont()->GetSize(); + std::unique_ptr font( + FPDFText_LoadFont(document(), data, size, FPDF_FONT_TYPE1, false)); + ASSERT_TRUE(font.get()); + CPDF_Font* typed_font = static_cast(font.get()); + EXPECT_TRUE(typed_font->IsType1Font()); + + CPDF_Dictionary* font_dict = typed_font->GetFontDict(); + EXPECT_EQ("Font", font_dict->GetStringFor("Type")); + EXPECT_EQ("Type1", font_dict->GetStringFor("Subtype")); + EXPECT_EQ("Times New Roman Bold", font_dict->GetStringFor("BaseFont")); + ASSERT_TRUE(font_dict->KeyExist("FirstChar")); + ASSERT_TRUE(font_dict->KeyExist("LastChar")); + EXPECT_EQ(32, font_dict->GetIntegerFor("FirstChar")); + EXPECT_EQ(255, font_dict->GetIntegerFor("LastChar")); + + CPDF_Array* widths_array = font_dict->GetArrayFor("Widths"); + ASSERT_TRUE(widths_array); + ASSERT_EQ(224U, widths_array->GetCount()); + EXPECT_EQ(250, widths_array->GetNumberAt(0)); + EXPECT_EQ(569, widths_array->GetNumberAt(11)); + EXPECT_EQ(500, widths_array->GetNumberAt(223)); + CheckFontDescriptor(font_dict, FPDF_FONT_TYPE1, true, false, size, data); +} + +TEST_F(FPDFEditEmbeddertest, LoadSimpleTrueTypeFont) { + CreateNewDocument(); + const CPDF_Font* stock_font = CPDF_Font::GetStockFont(cpdf_doc(), "Courier"); + const uint8_t* data = stock_font->GetFont()->GetFontData(); + const uint32_t size = stock_font->GetFont()->GetSize(); + std::unique_ptr font( + FPDFText_LoadFont(document(), data, size, FPDF_FONT_TRUETYPE, false)); + ASSERT_TRUE(font.get()); + CPDF_Font* typed_font = static_cast(font.get()); + EXPECT_TRUE(typed_font->IsTrueTypeFont()); + + CPDF_Dictionary* font_dict = typed_font->GetFontDict(); + EXPECT_EQ("Font", font_dict->GetStringFor("Type")); + EXPECT_EQ("TrueType", font_dict->GetStringFor("Subtype")); + EXPECT_EQ("Courier New", font_dict->GetStringFor("BaseFont")); + ASSERT_TRUE(font_dict->KeyExist("FirstChar")); + ASSERT_TRUE(font_dict->KeyExist("LastChar")); + EXPECT_EQ(32, font_dict->GetIntegerFor("FirstChar")); + EXPECT_EQ(255, font_dict->GetIntegerFor("LastChar")); + + CPDF_Array* widths_array = font_dict->GetArrayFor("Widths"); + ASSERT_TRUE(widths_array); + ASSERT_EQ(224U, widths_array->GetCount()); + EXPECT_EQ(600, widths_array->GetNumberAt(33)); + EXPECT_EQ(600, widths_array->GetNumberAt(74)); + EXPECT_EQ(600, widths_array->GetNumberAt(223)); + CheckFontDescriptor(font_dict, FPDF_FONT_TRUETYPE, false, false, size, data); +} + +TEST_F(FPDFEditEmbeddertest, LoadCIDType0Font) { + CreateNewDocument(); + const CPDF_Font* stock_font = + CPDF_Font::GetStockFont(cpdf_doc(), "Times-Roman"); + const uint8_t* data = stock_font->GetFont()->GetFontData(); + const uint32_t size = stock_font->GetFont()->GetSize(); + std::unique_ptr font( + FPDFText_LoadFont(document(), data, size, FPDF_FONT_TYPE1, 1)); + ASSERT_TRUE(font.get()); + CPDF_Font* typed_font = static_cast(font.get()); + EXPECT_TRUE(typed_font->IsCIDFont()); + + // Check font dictionary entries + CPDF_Dictionary* font_dict = typed_font->GetFontDict(); + EXPECT_EQ("Font", font_dict->GetStringFor("Type")); + EXPECT_EQ("Type0", font_dict->GetStringFor("Subtype")); + EXPECT_EQ("Times New Roman-Identity-H", font_dict->GetStringFor("BaseFont")); + EXPECT_EQ("Identity-H", font_dict->GetStringFor("Encoding")); + CPDF_Array* descendant_array = font_dict->GetArrayFor("DescendantFonts"); + ASSERT_TRUE(descendant_array); + EXPECT_EQ(1U, descendant_array->GetCount()); + + // Check the CIDFontDict + CPDF_Dictionary* cidfont_dict = descendant_array->GetDictAt(0); + EXPECT_EQ("Font", cidfont_dict->GetStringFor("Type")); + EXPECT_EQ("CIDFontType0", cidfont_dict->GetStringFor("Subtype")); + EXPECT_EQ("Times New Roman", cidfont_dict->GetStringFor("BaseFont")); + CPDF_Dictionary* cidinfo_dict = cidfont_dict->GetDictFor("CIDSystemInfo"); + ASSERT_TRUE(cidinfo_dict); + EXPECT_EQ("Adobe", cidinfo_dict->GetStringFor("Registry")); + EXPECT_EQ("Identity", cidinfo_dict->GetStringFor("Ordering")); + EXPECT_EQ(0, cidinfo_dict->GetNumberFor("Supplement")); + CheckFontDescriptor(cidfont_dict, FPDF_FONT_TYPE1, false, false, size, data); + + // Check widths + CPDF_Array* widths_array = cidfont_dict->GetArrayFor("W"); + ASSERT_TRUE(widths_array); + EXPECT_GT(widths_array->GetCount(), 1U); + CheckCompositeFontWidths(widths_array, typed_font); +} + +TEST_F(FPDFEditEmbeddertest, LoadCIDType2Font) { + CreateNewDocument(); + const CPDF_Font* stock_font = + CPDF_Font::GetStockFont(cpdf_doc(), "Helvetica-Oblique"); + const uint8_t* data = stock_font->GetFont()->GetFontData(); + const uint32_t size = stock_font->GetFont()->GetSize(); + + std::unique_ptr font( + FPDFText_LoadFont(document(), data, size, FPDF_FONT_TRUETYPE, 1)); + ASSERT_TRUE(font.get()); + CPDF_Font* typed_font = static_cast(font.get()); + EXPECT_TRUE(typed_font->IsCIDFont()); + + // Check font dictionary entries + CPDF_Dictionary* font_dict = typed_font->GetFontDict(); + EXPECT_EQ("Font", font_dict->GetStringFor("Type")); + EXPECT_EQ("Type0", font_dict->GetStringFor("Subtype")); + EXPECT_EQ("Arial Italic", font_dict->GetStringFor("BaseFont")); + EXPECT_EQ("Identity-H", font_dict->GetStringFor("Encoding")); + CPDF_Array* descendant_array = font_dict->GetArrayFor("DescendantFonts"); + ASSERT_TRUE(descendant_array); + EXPECT_EQ(1U, descendant_array->GetCount()); + + // Check the CIDFontDict + CPDF_Dictionary* cidfont_dict = descendant_array->GetDictAt(0); + EXPECT_EQ("Font", cidfont_dict->GetStringFor("Type")); + EXPECT_EQ("CIDFontType2", cidfont_dict->GetStringFor("Subtype")); + EXPECT_EQ("Arial Italic", cidfont_dict->GetStringFor("BaseFont")); + CPDF_Dictionary* cidinfo_dict = cidfont_dict->GetDictFor("CIDSystemInfo"); + ASSERT_TRUE(cidinfo_dict); + EXPECT_EQ("Adobe", cidinfo_dict->GetStringFor("Registry")); + EXPECT_EQ("Identity", cidinfo_dict->GetStringFor("Ordering")); + EXPECT_EQ(0, cidinfo_dict->GetNumberFor("Supplement")); + CheckFontDescriptor(cidfont_dict, FPDF_FONT_TRUETYPE, false, true, size, + data); + + // Check widths + CPDF_Array* widths_array = cidfont_dict->GetArrayFor("W"); + ASSERT_TRUE(widths_array); + CheckCompositeFontWidths(widths_array, typed_font); +} + +TEST_F(FPDFEditEmbeddertest, NormalizeNegativeRotation) { + // Load document with a -90 degree rotation + EXPECT_TRUE(OpenDocument("bug_713197.pdf")); + FPDF_PAGE page = LoadPage(0); + EXPECT_NE(nullptr, page); + + EXPECT_EQ(3, FPDFPage_GetRotation(page)); + UnloadPage(page); +} + +TEST_F(FPDFEditEmbeddertest, AddTrueTypeFontText) { + // Start with a blank page + FPDF_PAGE page = FPDFPage_New(CreateNewDocument(), 0, 612, 792); + { + const CPDF_Font* stock_font = CPDF_Font::GetStockFont(cpdf_doc(), "Arial"); + const uint8_t* data = stock_font->GetFont()->GetFontData(); + const uint32_t size = stock_font->GetFont()->GetSize(); + std::unique_ptr font( + FPDFText_LoadFont(document(), data, size, FPDF_FONT_TRUETYPE, 0)); + ASSERT_TRUE(font.get()); + + // Add some text to the page + FPDF_PAGEOBJECT text_object = + FPDFPageObj_CreateTextObj(document(), font.get(), 12.0f); + EXPECT_TRUE(text_object); + std::unique_ptr text = + GetFPDFWideString(L"I am testing my loaded font, WEE."); + EXPECT_TRUE(FPDFText_SetText(text_object, text.get())); + FPDFPageObj_Transform(text_object, 1, 0, 0, 1, 400, 400); + FPDFPage_InsertObject(page, text_object); + std::unique_ptr page_bitmap = + RenderPageWithFlags(page, nullptr, 0); +#if _FX_PLATFORM_ == _FX_PLATFORM_APPLE_ + const char md5[] = "17d2b6cd574cf66170b09c8927529a94"; +#else + const char md5[] = "70592859010ffbf532a2237b8118bcc4"; +#endif // _FX_PLATFORM_ == _FX_PLATFORM_APPLE_ + CompareBitmap(page_bitmap.get(), 612, 792, md5); + + // Add some more text, same font + FPDF_PAGEOBJECT text_object2 = + FPDFPageObj_CreateTextObj(document(), font.get(), 15.0f); + std::unique_ptr text2 = + GetFPDFWideString(L"Bigger font size"); + EXPECT_TRUE(FPDFText_SetText(text_object2, text2.get())); + FPDFPageObj_Transform(text_object2, 1, 0, 0, 1, 200, 200); + FPDFPage_InsertObject(page, text_object2); + } + std::unique_ptr page_bitmap2 = + RenderPageWithFlags(page, nullptr, 0); +#if _FX_PLATFORM_ == _FX_PLATFORM_APPLE_ + const char md5_2[] = "8eded4193ff1f0f77b8b600a825e97ea"; +#else + const char md5_2[] = "c1d10cce1761c4a998a16b2562030568"; +#endif // _FX_PLATFORM_ == _FX_PLATFORM_APPLE_ + CompareBitmap(page_bitmap2.get(), 612, 792, md5_2); + + EXPECT_TRUE(FPDFPage_GenerateContent(page)); + EXPECT_TRUE(FPDF_SaveAsCopy(document(), this, 0)); + FPDF_ClosePage(page); + + VerifySavedDocument(612, 792, md5_2); +} + +TEST_F(FPDFEditEmbeddertest, TransformAnnot) { + // Open a file with one annotation and load its first page. + ASSERT_TRUE(OpenDocument("annotation_highlight_long_content.pdf")); + FPDF_PAGE page = LoadPage(0); + ASSERT_TRUE(page); + + { + // Add an underline annotation to the page without specifying its rectangle. + std::unique_ptr annot( + FPDFPage_CreateAnnot(page, FPDF_ANNOT_UNDERLINE)); + ASSERT_TRUE(annot); + + // FPDFPage_TransformAnnots() should run without errors when modifying + // annotation rectangles. + FPDFPage_TransformAnnots(page, 1, 2, 3, 4, 5, 6); + } + UnloadPage(page); +} + +// TODO(npm): Add tests using Japanese fonts in other OS. +#if _FX_PLATFORM_ == _FX_PLATFORM_LINUX_ +TEST_F(FPDFEditEmbeddertest, AddCIDFontText) { + // Start with a blank page + FPDF_PAGE page = FPDFPage_New(CreateNewDocument(), 0, 612, 792); + CFX_Font CIDfont; + { + // First, get the data from the font + CIDfont.LoadSubst("IPAGothic", 1, 0, 400, 0, 932, 0); + EXPECT_EQ("IPAGothic", CIDfont.GetFaceName()); + const uint8_t* data = CIDfont.GetFontData(); + const uint32_t size = CIDfont.GetSize(); + + // Load the data into a FPDF_Font. + std::unique_ptr font( + FPDFText_LoadFont(document(), data, size, FPDF_FONT_TRUETYPE, 1)); + ASSERT_TRUE(font.get()); + + // Add some text to the page + FPDF_PAGEOBJECT text_object = + FPDFPageObj_CreateTextObj(document(), font.get(), 12.0f); + ASSERT_TRUE(text_object); + std::wstring wstr = L"ABCDEFGhijklmnop."; + std::unique_ptr text = + GetFPDFWideString(wstr); + EXPECT_TRUE(FPDFText_SetText(text_object, text.get())); + FPDFPageObj_Transform(text_object, 1, 0, 0, 1, 200, 200); + FPDFPage_InsertObject(page, text_object); + + // And add some Japanese characters + FPDF_PAGEOBJECT text_object2 = + FPDFPageObj_CreateTextObj(document(), font.get(), 18.0f); + ASSERT_TRUE(text_object2); + std::wstring wstr2 = + L"\u3053\u3093\u306B\u3061\u306f\u4e16\u754C\u3002\u3053\u3053\u306B1" + L"\u756A"; + std::unique_ptr text2 = + GetFPDFWideString(wstr2); + EXPECT_TRUE(FPDFText_SetText(text_object2, text2.get())); + FPDFPageObj_Transform(text_object2, 1, 0, 0, 1, 100, 500); + FPDFPage_InsertObject(page, text_object2); + } + + // Check that the text renders properly. + const char md5[] = "c68cd79aa72bf83a7b25271370d46b21"; + { + std::unique_ptr page_bitmap = + RenderPageWithFlags(page, nullptr, 0); + CompareBitmap(page_bitmap.get(), 612, 792, md5); + } + + // Save the document, close the page. + EXPECT_TRUE(FPDFPage_GenerateContent(page)); + EXPECT_TRUE(FPDF_SaveAsCopy(document(), this, 0)); + FPDF_ClosePage(page); + + VerifySavedDocument(612, 792, md5); +} +#endif // _FX_PLATFORM_ == _FX_PLATFORM_LINUX_ + +TEST_F(FPDFEditEmbeddertest, SaveAndRender) { + const char md5[] = "3c20472b0552c0c22b88ab1ed8c6202b"; + { + EXPECT_TRUE(OpenDocument("bug_779.pdf")); + FPDF_PAGE page = LoadPage(0); + ASSERT_NE(nullptr, page); + + // Now add a more complex blue path. + FPDF_PAGEOBJECT green_path = FPDFPageObj_CreateNewPath(20, 20); + EXPECT_TRUE(FPDFPath_SetFillColor(green_path, 0, 255, 0, 200)); + // TODO(npm): stroking will cause the MD5s to differ. + EXPECT_TRUE(FPDFPath_SetDrawMode(green_path, FPDF_FILLMODE_WINDING, 0)); + EXPECT_TRUE(FPDFPath_LineTo(green_path, 20, 63)); + EXPECT_TRUE(FPDFPath_BezierTo(green_path, 55, 55, 78, 78, 90, 90)); + EXPECT_TRUE(FPDFPath_LineTo(green_path, 133, 133)); + EXPECT_TRUE(FPDFPath_LineTo(green_path, 133, 33)); + EXPECT_TRUE(FPDFPath_BezierTo(green_path, 38, 33, 39, 36, 40, 40)); + EXPECT_TRUE(FPDFPath_Close(green_path)); + FPDFPage_InsertObject(page, green_path); + std::unique_ptr page_bitmap = + RenderLoadedPage(page); + CompareBitmap(page_bitmap.get(), 612, 792, md5); + + // Now save the result, closing the page and document + EXPECT_TRUE(FPDFPage_GenerateContent(page)); + EXPECT_TRUE(FPDF_SaveAsCopy(document(), this, 0)); + UnloadPage(page); + } + + VerifySavedDocument(612, 792, md5); +} + +TEST_F(FPDFEditEmbeddertest, ExtractImageBitmap) { + ASSERT_TRUE(OpenDocument("embedded_images.pdf")); + FPDF_PAGE page = LoadPage(0); + ASSERT_TRUE(page); + ASSERT_EQ(39, FPDFPage_CountObjects(page)); + + FPDF_PAGEOBJECT obj = FPDFPage_GetObject(page, 32); + EXPECT_NE(FPDF_PAGEOBJ_IMAGE, FPDFPageObj_GetType(obj)); + EXPECT_FALSE(FPDFImageObj_GetBitmap(obj)); + + obj = FPDFPage_GetObject(page, 33); + ASSERT_EQ(FPDF_PAGEOBJ_IMAGE, FPDFPageObj_GetType(obj)); + FPDF_BITMAP bitmap = FPDFImageObj_GetBitmap(obj); + EXPECT_EQ(FPDFBitmap_BGR, FPDFBitmap_GetFormat(bitmap)); + CompareBitmap(bitmap, 109, 88, "d65e98d968d196abf13f78aec655ffae"); + FPDFBitmap_Destroy(bitmap); + + obj = FPDFPage_GetObject(page, 34); + ASSERT_EQ(FPDF_PAGEOBJ_IMAGE, FPDFPageObj_GetType(obj)); + bitmap = FPDFImageObj_GetBitmap(obj); + EXPECT_EQ(FPDFBitmap_BGR, FPDFBitmap_GetFormat(bitmap)); + CompareBitmap(bitmap, 103, 75, "1287711c84dbef767c435d11697661d6"); + FPDFBitmap_Destroy(bitmap); + + obj = FPDFPage_GetObject(page, 35); + ASSERT_EQ(FPDF_PAGEOBJ_IMAGE, FPDFPageObj_GetType(obj)); + bitmap = FPDFImageObj_GetBitmap(obj); + EXPECT_EQ(FPDFBitmap_Gray, FPDFBitmap_GetFormat(bitmap)); + CompareBitmap(bitmap, 92, 68, "9c6d76cb1e37ef8514f9455d759391f3"); + FPDFBitmap_Destroy(bitmap); + + obj = FPDFPage_GetObject(page, 36); + ASSERT_EQ(FPDF_PAGEOBJ_IMAGE, FPDFPageObj_GetType(obj)); + bitmap = FPDFImageObj_GetBitmap(obj); + EXPECT_EQ(FPDFBitmap_BGR, FPDFBitmap_GetFormat(bitmap)); + CompareBitmap(bitmap, 79, 60, "15cb6a49a2e354ed0e9f45dd34e3da1a"); + FPDFBitmap_Destroy(bitmap); + + obj = FPDFPage_GetObject(page, 37); + ASSERT_EQ(FPDF_PAGEOBJ_IMAGE, FPDFPageObj_GetType(obj)); + bitmap = FPDFImageObj_GetBitmap(obj); + EXPECT_EQ(FPDFBitmap_BGR, FPDFBitmap_GetFormat(bitmap)); + CompareBitmap(bitmap, 126, 106, "be5a64ba7890d2657522af6524118534"); + FPDFBitmap_Destroy(bitmap); + + obj = FPDFPage_GetObject(page, 38); + ASSERT_EQ(FPDF_PAGEOBJ_IMAGE, FPDFPageObj_GetType(obj)); + bitmap = FPDFImageObj_GetBitmap(obj); + EXPECT_EQ(FPDFBitmap_BGR, FPDFBitmap_GetFormat(bitmap)); + CompareBitmap(bitmap, 194, 119, "f9e24207ee1bc0db6c543d33a5f12ec5"); + FPDFBitmap_Destroy(bitmap); + UnloadPage(page); +} + +TEST_F(FPDFEditEmbeddertest, ExtractJBigImageBitmap) { + ASSERT_TRUE(OpenDocument("bug_631912.pdf")); + FPDF_PAGE page = LoadPage(0); + ASSERT_TRUE(page); + ASSERT_EQ(1, FPDFPage_CountObjects(page)); + + FPDF_PAGEOBJECT obj = FPDFPage_GetObject(page, 0); + ASSERT_EQ(FPDF_PAGEOBJ_IMAGE, FPDFPageObj_GetType(obj)); + { + std::unique_ptr bitmap( + FPDFImageObj_GetBitmap(obj)); + ASSERT_TRUE(bitmap); + EXPECT_EQ(FPDFBitmap_Gray, FPDFBitmap_GetFormat(bitmap.get())); + CompareBitmap(bitmap.get(), 1152, 720, "3f6a48e2b3e91b799bf34567f55cb4de"); + } + + UnloadPage(page); +} + +TEST_F(FPDFEditEmbeddertest, GetImageData) { + EXPECT_TRUE(OpenDocument("embedded_images.pdf")); + FPDF_PAGE page = LoadPage(0); + ASSERT_TRUE(page); + ASSERT_EQ(39, FPDFPage_CountObjects(page)); + + // Retrieve an image object with flate-encoded data stream. + FPDF_PAGEOBJECT obj = FPDFPage_GetObject(page, 33); + ASSERT_EQ(FPDF_PAGEOBJ_IMAGE, FPDFPageObj_GetType(obj)); + + // Check that the raw image data has the correct length and hash value. + unsigned long len = FPDFImageObj_GetImageDataRaw(obj, nullptr, 0); + std::vector buf(len); + EXPECT_EQ(4091u, FPDFImageObj_GetImageDataRaw(obj, buf.data(), len)); + EXPECT_EQ("f73802327d2e88e890f653961bcda81a", + GenerateMD5Base16(reinterpret_cast(buf.data()), len)); + + // Check that the decoded image data has the correct length and hash value. + len = FPDFImageObj_GetImageDataDecoded(obj, nullptr, 0); + buf.clear(); + buf.resize(len); + EXPECT_EQ(28776u, FPDFImageObj_GetImageDataDecoded(obj, buf.data(), len)); + EXPECT_EQ("cb3637934bb3b95a6e4ae1ea9eb9e56e", + GenerateMD5Base16(reinterpret_cast(buf.data()), len)); + + // Retrieve an image obejct with DCTDecode-encoded data stream. + obj = FPDFPage_GetObject(page, 37); + ASSERT_EQ(FPDF_PAGEOBJ_IMAGE, FPDFPageObj_GetType(obj)); + + // Check that the raw image data has the correct length and hash value. + len = FPDFImageObj_GetImageDataRaw(obj, nullptr, 0); + buf.clear(); + buf.resize(len); + EXPECT_EQ(4370u, FPDFImageObj_GetImageDataRaw(obj, buf.data(), len)); + EXPECT_EQ("6aae1f3710335023a9e12191be66b64b", + GenerateMD5Base16(reinterpret_cast(buf.data()), len)); + + // Check that the decoded image data has the correct length and hash value, + // which should be the same as those of the raw data, since this image is + // encoded by a single DCTDecode filter and decoding is a noop. + len = FPDFImageObj_GetImageDataDecoded(obj, nullptr, 0); + buf.clear(); + buf.resize(len); + EXPECT_EQ(4370u, FPDFImageObj_GetImageDataDecoded(obj, buf.data(), len)); + EXPECT_EQ("6aae1f3710335023a9e12191be66b64b", + GenerateMD5Base16(reinterpret_cast(buf.data()), len)); + + UnloadPage(page); +} + +TEST_F(FPDFEditEmbeddertest, DestroyPageObject) { + FPDF_PAGEOBJECT rect = FPDFPageObj_CreateNewRect(10, 10, 20, 20); + ASSERT_TRUE(rect); + + // There should be no memory leaks with a call to FPDFPageObj_Destroy(). + FPDFPageObj_Destroy(rect); +} + +TEST_F(FPDFEditEmbeddertest, GetImageFilters) { + EXPECT_TRUE(OpenDocument("embedded_images.pdf")); + FPDF_PAGE page = LoadPage(0); + ASSERT_TRUE(page); + + // Verify that retrieving the filter of a non-image object would fail. + FPDF_PAGEOBJECT obj = FPDFPage_GetObject(page, 32); + ASSERT_NE(FPDF_PAGEOBJ_IMAGE, FPDFPageObj_GetType(obj)); + ASSERT_EQ(0, FPDFImageObj_GetImageFilterCount(obj)); + EXPECT_EQ(0u, FPDFImageObj_GetImageFilter(obj, 0, nullptr, 0)); + + // Verify the returned filter string for an image object with a single filter. + obj = FPDFPage_GetObject(page, 33); + ASSERT_EQ(FPDF_PAGEOBJ_IMAGE, FPDFPageObj_GetType(obj)); + ASSERT_EQ(1, FPDFImageObj_GetImageFilterCount(obj)); + unsigned long len = FPDFImageObj_GetImageFilter(obj, 0, nullptr, 0); + std::vector buf(len); + static constexpr char kFlateDecode[] = "FlateDecode"; + EXPECT_EQ(sizeof(kFlateDecode), + FPDFImageObj_GetImageFilter(obj, 0, buf.data(), len)); + EXPECT_STREQ(kFlateDecode, buf.data()); + EXPECT_EQ(0u, FPDFImageObj_GetImageFilter(obj, 1, nullptr, 0)); + + // Verify all the filters for an image object with a list of filters. + obj = FPDFPage_GetObject(page, 38); + ASSERT_EQ(FPDF_PAGEOBJ_IMAGE, FPDFPageObj_GetType(obj)); + ASSERT_EQ(2, FPDFImageObj_GetImageFilterCount(obj)); + len = FPDFImageObj_GetImageFilter(obj, 0, nullptr, 0); + buf.clear(); + buf.resize(len); + static constexpr char kASCIIHexDecode[] = "ASCIIHexDecode"; + EXPECT_EQ(sizeof(kASCIIHexDecode), + FPDFImageObj_GetImageFilter(obj, 0, buf.data(), len)); + EXPECT_STREQ(kASCIIHexDecode, buf.data()); + + len = FPDFImageObj_GetImageFilter(obj, 1, nullptr, 0); + buf.clear(); + buf.resize(len); + static constexpr char kDCTDecode[] = "DCTDecode"; + EXPECT_EQ(sizeof(kDCTDecode), + FPDFImageObj_GetImageFilter(obj, 1, buf.data(), len)); + EXPECT_STREQ(kDCTDecode, buf.data()); + + UnloadPage(page); +} + +TEST_F(FPDFEditEmbeddertest, GetImageMetadata) { + ASSERT_TRUE(OpenDocument("embedded_images.pdf")); + FPDF_PAGE page = LoadPage(0); + ASSERT_TRUE(page); + + // Check that getting the metadata of a null object would fail. + FPDF_IMAGEOBJ_METADATA metadata; + EXPECT_FALSE(FPDFImageObj_GetImageMetadata(nullptr, page, &metadata)); + + // Check that receiving the metadata with a null metadata object would fail. + FPDF_PAGEOBJECT obj = FPDFPage_GetObject(page, 35); + EXPECT_FALSE(FPDFImageObj_GetImageMetadata(obj, page, nullptr)); + + // Check that when retrieving an image object's metadata without passing in + // |page|, all values are correct, with the last two being default values. + ASSERT_EQ(FPDF_PAGEOBJ_IMAGE, FPDFPageObj_GetType(obj)); + ASSERT_TRUE(FPDFImageObj_GetImageMetadata(obj, nullptr, &metadata)); + EXPECT_EQ(7, metadata.marked_content_id); + EXPECT_EQ(92u, metadata.width); + EXPECT_EQ(68u, metadata.height); + EXPECT_NEAR(96.000000, metadata.horizontal_dpi, 0.001); + EXPECT_NEAR(96.000000, metadata.vertical_dpi, 0.001); + EXPECT_EQ(0u, metadata.bits_per_pixel); + EXPECT_EQ(FPDF_COLORSPACE_UNKNOWN, metadata.colorspace); + + // Verify the metadata of a bitmap image with indexed colorspace. + ASSERT_TRUE(FPDFImageObj_GetImageMetadata(obj, page, &metadata)); + EXPECT_EQ(7, metadata.marked_content_id); + EXPECT_EQ(92u, metadata.width); + EXPECT_EQ(68u, metadata.height); + EXPECT_NEAR(96.000000, metadata.horizontal_dpi, 0.001); + EXPECT_NEAR(96.000000, metadata.vertical_dpi, 0.001); + EXPECT_EQ(1u, metadata.bits_per_pixel); + EXPECT_EQ(FPDF_COLORSPACE_INDEXED, metadata.colorspace); + + // Verify the metadata of an image with RGB colorspace. + obj = FPDFPage_GetObject(page, 37); + ASSERT_EQ(FPDF_PAGEOBJ_IMAGE, FPDFPageObj_GetType(obj)); + ASSERT_TRUE(FPDFImageObj_GetImageMetadata(obj, page, &metadata)); + EXPECT_EQ(9, metadata.marked_content_id); + EXPECT_EQ(126u, metadata.width); + EXPECT_EQ(106u, metadata.height); + EXPECT_NEAR(162.173752, metadata.horizontal_dpi, 0.001); + EXPECT_NEAR(162.555878, metadata.vertical_dpi, 0.001); + EXPECT_EQ(24u, metadata.bits_per_pixel); + EXPECT_EQ(FPDF_COLORSPACE_DEVICERGB, metadata.colorspace); + + UnloadPage(page); +} diff --git a/fpdfsdk/fpdf_editimg.cpp b/fpdfsdk/fpdf_editimg.cpp new file mode 100644 index 0000000000..f1d90ece2d --- /dev/null +++ b/fpdfsdk/fpdf_editimg.cpp @@ -0,0 +1,310 @@ +// Copyright 2014 PDFium Authors. All rights reserved. +// 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_edit.h" + +#include "core/fpdfapi/cpdf_modulemgr.h" +#include "core/fpdfapi/page/cpdf_image.h" +#include "core/fpdfapi/page/cpdf_imageobject.h" +#include "core/fpdfapi/page/cpdf_page.h" +#include "core/fpdfapi/page/cpdf_pageobject.h" +#include "core/fpdfapi/parser/cpdf_array.h" +#include "core/fpdfapi/parser/cpdf_name.h" +#include "core/fpdfapi/render/cpdf_dibsource.h" +#include "fpdfsdk/cpdfsdk_customaccess.h" +#include "fpdfsdk/cpdfsdk_helpers.h" +#include "third_party/base/ptr_util.h" + +namespace { + +// These checks ensure the consistency of colorspace values across core/ and +// public/. +static_assert(PDFCS_DEVICEGRAY == FPDF_COLORSPACE_DEVICEGRAY, + "PDFCS_DEVICEGRAY value mismatch"); +static_assert(PDFCS_DEVICERGB == FPDF_COLORSPACE_DEVICERGB, + "PDFCS_DEVICERGB value mismatch"); +static_assert(PDFCS_DEVICECMYK == FPDF_COLORSPACE_DEVICECMYK, + "PDFCS_DEVICECMYK value mismatch"); +static_assert(PDFCS_CALGRAY == FPDF_COLORSPACE_CALGRAY, + "PDFCS_CALGRAY value mismatch"); +static_assert(PDFCS_CALRGB == FPDF_COLORSPACE_CALRGB, + "PDFCS_CALRGB value mismatch"); +static_assert(PDFCS_LAB == FPDF_COLORSPACE_LAB, "PDFCS_LAB value mismatch"); +static_assert(PDFCS_ICCBASED == FPDF_COLORSPACE_ICCBASED, + "PDFCS_ICCBASED value mismatch"); +static_assert(PDFCS_SEPARATION == FPDF_COLORSPACE_SEPARATION, + "PDFCS_SEPARATION value mismatch"); +static_assert(PDFCS_DEVICEN == FPDF_COLORSPACE_DEVICEN, + "PDFCS_DEVICEN value mismatch"); +static_assert(PDFCS_INDEXED == FPDF_COLORSPACE_INDEXED, + "PDFCS_INDEXED value mismatch"); +static_assert(PDFCS_PATTERN == FPDF_COLORSPACE_PATTERN, + "PDFCS_PATTERN value mismatch"); + +RetainPtr MakeSeekableReadStream( + FPDF_FILEACCESS* pFileAccess) { + return pdfium::MakeRetain(pFileAccess); +} + +bool LoadJpegHelper(FPDF_PAGE* pages, + int nCount, + FPDF_PAGEOBJECT image_object, + FPDF_FILEACCESS* fileAccess, + bool inlineJpeg) { + if (!image_object || !fileAccess) + return false; + + RetainPtr pFile = MakeSeekableReadStream(fileAccess); + CPDF_ImageObject* pImgObj = static_cast(image_object); + + if (pages) { + for (int index = 0; index < nCount; index++) { + CPDF_Page* pPage = CPDFPageFromFPDFPage(pages[index]); + if (pPage) + pImgObj->GetImage()->ResetCache(pPage, nullptr); + } + } + + if (inlineJpeg) + pImgObj->GetImage()->SetJpegImageInline(pFile); + else + pImgObj->GetImage()->SetJpegImage(pFile); + pImgObj->SetDirty(true); + return true; +} + +} // namespace + +FPDF_EXPORT FPDF_PAGEOBJECT FPDF_CALLCONV +FPDFPageObj_NewImageObj(FPDF_DOCUMENT document) { + CPDF_Document* pDoc = CPDFDocumentFromFPDFDocument(document); + if (!pDoc) + return nullptr; + + auto pImageObj = pdfium::MakeUnique(); + pImageObj->SetImage(pdfium::MakeRetain(pDoc)); + return pImageObj.release(); +} + +FPDF_EXPORT FPDF_BOOL FPDF_CALLCONV +FPDFImageObj_LoadJpegFile(FPDF_PAGE* pages, + int nCount, + FPDF_PAGEOBJECT image_object, + FPDF_FILEACCESS* fileAccess) { + return LoadJpegHelper(pages, nCount, image_object, fileAccess, false); +} + +FPDF_EXPORT FPDF_BOOL FPDF_CALLCONV +FPDFImageObj_LoadJpegFileInline(FPDF_PAGE* pages, + int nCount, + FPDF_PAGEOBJECT image_object, + FPDF_FILEACCESS* fileAccess) { + return LoadJpegHelper(pages, nCount, image_object, fileAccess, true); +} + +FPDF_EXPORT FPDF_BOOL FPDF_CALLCONV +FPDFImageObj_SetMatrix(FPDF_PAGEOBJECT image_object, + double a, + double b, + double c, + double d, + double e, + double f) { + if (!image_object) + return false; + + CPDF_ImageObject* pImgObj = static_cast(image_object); + pImgObj->set_matrix(CFX_Matrix(static_cast(a), static_cast(b), + static_cast(c), static_cast(d), + static_cast(e), static_cast(f))); + pImgObj->CalcBoundingBox(); + pImgObj->SetDirty(true); + return true; +} + +FPDF_EXPORT FPDF_BOOL FPDF_CALLCONV +FPDFImageObj_SetBitmap(FPDF_PAGE* pages, + int nCount, + FPDF_PAGEOBJECT image_object, + FPDF_BITMAP bitmap) { + if (!image_object || !bitmap || !pages) + return false; + + CPDF_ImageObject* pImgObj = static_cast(image_object); + for (int index = 0; index < nCount; index++) { + CPDF_Page* pPage = CPDFPageFromFPDFPage(pages[index]); + if (pPage) + pImgObj->GetImage()->ResetCache(pPage, nullptr); + } + RetainPtr holder(CFXBitmapFromFPDFBitmap(bitmap)); + pImgObj->GetImage()->SetImage(holder); + pImgObj->CalcBoundingBox(); + pImgObj->SetDirty(true); + return true; +} + +FPDF_EXPORT FPDF_BITMAP FPDF_CALLCONV +FPDFImageObj_GetBitmap(FPDF_PAGEOBJECT image_object) { + CPDF_PageObject* pObj = CPDFPageObjectFromFPDFPageObject(image_object); + if (!pObj || !pObj->IsImage()) + return nullptr; + + RetainPtr pImg = pObj->AsImage()->GetImage(); + if (!pImg) + return nullptr; + + RetainPtr pSource = pImg->LoadDIBSource(); + if (!pSource) + return nullptr; + + RetainPtr pBitmap; + // If the source image has a representation of 1 bit per pixel, then convert + // it to a grayscale bitmap having 1 byte per pixel, since bitmaps have no + // concept of bits. Otherwise, convert the source image to a bitmap directly, + // retaining its color representation. + if (pSource->GetBPP() == 1) + pBitmap = pSource->CloneConvert(FXDIB_8bppRgb); + else + pBitmap = pSource->Clone(nullptr); + + return pBitmap.Leak(); +} + +FPDF_EXPORT unsigned long FPDF_CALLCONV +FPDFImageObj_GetImageDataDecoded(FPDF_PAGEOBJECT image_object, + void* buffer, + unsigned long buflen) { + CPDF_PageObject* pObj = CPDFPageObjectFromFPDFPageObject(image_object); + if (!pObj || !pObj->IsImage()) + return 0; + + RetainPtr pImg = pObj->AsImage()->GetImage(); + if (!pImg) + return 0; + + CPDF_Stream* pImgStream = pImg->GetStream(); + if (!pImgStream) + return 0; + + return DecodeStreamMaybeCopyAndReturnLength(pImgStream, buffer, buflen); +} + +FPDF_EXPORT unsigned long FPDF_CALLCONV +FPDFImageObj_GetImageDataRaw(FPDF_PAGEOBJECT image_object, + void* buffer, + unsigned long buflen) { + CPDF_PageObject* pObj = CPDFPageObjectFromFPDFPageObject(image_object); + if (!pObj || !pObj->IsImage()) + return 0; + + RetainPtr pImg = pObj->AsImage()->GetImage(); + if (!pImg) + return 0; + + CPDF_Stream* pImgStream = pImg->GetStream(); + if (!pImgStream) + return 0; + + uint32_t len = pImgStream->GetRawSize(); + if (buffer && buflen >= len) + memcpy(buffer, pImgStream->GetRawData(), len); + + return len; +} + +FPDF_EXPORT int FPDF_CALLCONV +FPDFImageObj_GetImageFilterCount(FPDF_PAGEOBJECT image_object) { + CPDF_PageObject* pObj = CPDFPageObjectFromFPDFPageObject(image_object); + if (!pObj || !pObj->IsImage()) + return 0; + + RetainPtr pImg = pObj->AsImage()->GetImage(); + if (!pImg) + return 0; + + CPDF_Dictionary* pDict = pImg->GetDict(); + CPDF_Object* pFilter = pDict ? pDict->GetDirectObjectFor("Filter") : nullptr; + if (!pFilter) + return 0; + + if (pFilter->IsArray()) + return pFilter->AsArray()->GetCount(); + if (pFilter->IsName()) + return 1; + + return 0; +} + +FPDF_EXPORT unsigned long FPDF_CALLCONV +FPDFImageObj_GetImageFilter(FPDF_PAGEOBJECT image_object, + int index, + void* buffer, + unsigned long buflen) { + if (index < 0 || index >= FPDFImageObj_GetImageFilterCount(image_object)) + return 0; + + CPDF_PageObject* pObj = CPDFPageObjectFromFPDFPageObject(image_object); + CPDF_Object* pFilter = + pObj->AsImage()->GetImage()->GetDict()->GetDirectObjectFor("Filter"); + ByteString bsFilter; + if (pFilter->IsName()) + bsFilter = pFilter->AsName()->GetString(); + else + bsFilter = pFilter->AsArray()->GetStringAt(index); + + unsigned long len = bsFilter.GetLength() + 1; + if (buffer && len <= buflen) + memcpy(buffer, bsFilter.c_str(), len); + return len; +} + +FPDF_EXPORT FPDF_BOOL FPDF_CALLCONV +FPDFImageObj_GetImageMetadata(FPDF_PAGEOBJECT image_object, + FPDF_PAGE page, + FPDF_IMAGEOBJ_METADATA* metadata) { + CPDF_PageObject* pObj = CPDFPageObjectFromFPDFPageObject(image_object); + if (!pObj || !pObj->IsImage() || !metadata) + return false; + + RetainPtr pImg = pObj->AsImage()->GetImage(); + if (!pImg) + return false; + + metadata->marked_content_id = pObj->m_ContentMark.GetMarkedContentID(); + + const int nPixelWidth = pImg->GetPixelWidth(); + const int nPixelHeight = pImg->GetPixelHeight(); + metadata->width = nPixelWidth; + metadata->height = nPixelHeight; + + const float nWidth = pObj->m_Right - pObj->m_Left; + const float nHeight = pObj->m_Top - pObj->m_Bottom; + constexpr int nPointsPerInch = 72; + if (nWidth != 0 && nHeight != 0) { + metadata->horizontal_dpi = nPixelWidth / nWidth * nPointsPerInch; + metadata->vertical_dpi = nPixelHeight / nHeight * nPointsPerInch; + } + + metadata->bits_per_pixel = 0; + metadata->colorspace = FPDF_COLORSPACE_UNKNOWN; + + CPDF_Page* pPage = CPDFPageFromFPDFPage(page); + if (!pPage || !pPage->m_pDocument.Get() || !pImg->GetStream()) + return true; + + auto pSource = pdfium::MakeRetain(); + CPDF_DIBSource::LoadState ret = pSource->StartLoadDIBSource( + pPage->m_pDocument.Get(), pImg->GetStream(), false, nullptr, + pPage->m_pPageResources.Get()); + if (ret == CPDF_DIBSource::LoadState::kFail) + return true; + + metadata->bits_per_pixel = pSource->GetBPP(); + if (pSource->GetColorSpace()) + metadata->colorspace = pSource->GetColorSpace()->GetFamily(); + + return true; +} diff --git a/fpdfsdk/fpdf_editimg_unittest.cpp b/fpdfsdk/fpdf_editimg_unittest.cpp new file mode 100644 index 0000000000..fcc081ab76 --- /dev/null +++ b/fpdfsdk/fpdf_editimg_unittest.cpp @@ -0,0 +1,71 @@ +// Copyright 2016 PDFium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "public/fpdf_edit.h" + +#include "core/fpdfapi/cpdf_modulemgr.h" +#include "testing/gtest/include/gtest/gtest.h" + +class PDFEditTest : public testing::Test { + void SetUp() override { CPDF_ModuleMgr::Get()->Init(); } + + void TearDown() override { CPDF_ModuleMgr::Destroy(); } +}; + +TEST_F(PDFEditTest, InsertObjectWithInvalidPage) { + FPDF_DOCUMENT doc = FPDF_CreateNewDocument(); + FPDF_PAGE page = FPDFPage_New(doc, 0, 100, 100); + EXPECT_EQ(0, FPDFPage_CountObjects(page)); + + FPDFPage_InsertObject(nullptr, nullptr); + EXPECT_EQ(0, FPDFPage_CountObjects(page)); + + FPDFPage_InsertObject(page, nullptr); + EXPECT_EQ(0, FPDFPage_CountObjects(page)); + + FPDF_PAGEOBJECT page_image = FPDFPageObj_NewImageObj(doc); + FPDFPage_InsertObject(nullptr, page_image); + EXPECT_EQ(0, FPDFPage_CountObjects(page)); + + FPDF_ClosePage(page); + FPDF_CloseDocument(doc); +} + +TEST_F(PDFEditTest, NewImageObj) { + FPDF_DOCUMENT doc = FPDF_CreateNewDocument(); + FPDF_PAGE page = FPDFPage_New(doc, 0, 100, 100); + EXPECT_EQ(0, FPDFPage_CountObjects(page)); + + FPDF_PAGEOBJECT page_image = FPDFPageObj_NewImageObj(doc); + FPDFPage_InsertObject(page, page_image); + EXPECT_EQ(1, FPDFPage_CountObjects(page)); + EXPECT_TRUE(FPDFPage_GenerateContent(page)); + + FPDF_ClosePage(page); + FPDF_CloseDocument(doc); +} + +TEST_F(PDFEditTest, NewImageObjGenerateContent) { + FPDF_DOCUMENT doc = FPDF_CreateNewDocument(); + FPDF_PAGE page = FPDFPage_New(doc, 0, 100, 100); + EXPECT_EQ(0, FPDFPage_CountObjects(page)); + + constexpr int kBitmapSize = 50; + FPDF_BITMAP bitmap = FPDFBitmap_Create(kBitmapSize, kBitmapSize, 0); + FPDFBitmap_FillRect(bitmap, 0, 0, kBitmapSize, kBitmapSize, 0x00000000); + EXPECT_EQ(kBitmapSize, FPDFBitmap_GetWidth(bitmap)); + EXPECT_EQ(kBitmapSize, FPDFBitmap_GetHeight(bitmap)); + + FPDF_PAGEOBJECT page_image = FPDFPageObj_NewImageObj(doc); + ASSERT_TRUE(FPDFImageObj_SetBitmap(&page, 0, page_image, bitmap)); + ASSERT_TRUE( + FPDFImageObj_SetMatrix(page_image, kBitmapSize, 0, 0, kBitmapSize, 0, 0)); + FPDFPage_InsertObject(page, page_image); + EXPECT_EQ(1, FPDFPage_CountObjects(page)); + EXPECT_TRUE(FPDFPage_GenerateContent(page)); + + FPDFBitmap_Destroy(bitmap); + FPDF_ClosePage(page); + FPDF_CloseDocument(doc); +} diff --git a/fpdfsdk/fpdf_editpage.cpp b/fpdfsdk/fpdf_editpage.cpp new file mode 100644 index 0000000000..bc494f8d85 --- /dev/null +++ b/fpdfsdk/fpdf_editpage.cpp @@ -0,0 +1,427 @@ +// Copyright 2014 PDFium Authors. All rights reserved. +// 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_edit.h" + +#include +#include +#include + +#include "core/fpdfapi/edit/cpdf_pagecontentgenerator.h" +#include "core/fpdfapi/page/cpdf_form.h" +#include "core/fpdfapi/page/cpdf_formobject.h" +#include "core/fpdfapi/page/cpdf_imageobject.h" +#include "core/fpdfapi/page/cpdf_page.h" +#include "core/fpdfapi/page/cpdf_pageobject.h" +#include "core/fpdfapi/page/cpdf_pathobject.h" +#include "core/fpdfapi/page/cpdf_shadingobject.h" +#include "core/fpdfapi/parser/cpdf_array.h" +#include "core/fpdfapi/parser/cpdf_document.h" +#include "core/fpdfapi/parser/cpdf_number.h" +#include "core/fpdfapi/parser/cpdf_string.h" +#include "core/fpdfdoc/cpdf_annot.h" +#include "core/fpdfdoc/cpdf_annotlist.h" +#include "fpdfsdk/cpdfsdk_helpers.h" +#include "public/fpdf_formfill.h" +#include "third_party/base/logging.h" +#include "third_party/base/stl_util.h" + +#ifdef PDF_ENABLE_XFA +#include "fpdfsdk/fpdfxfa/cpdfxfa_context.h" +#include "fpdfsdk/fpdfxfa/cpdfxfa_page.h" +#endif // PDF_ENABLE_XFA + +#if _FX_OS_ == _FX_OS_ANDROID_ +#include +#else +#include +#endif + +namespace { + +static_assert(FPDF_PAGEOBJ_TEXT == CPDF_PageObject::TEXT, + "FPDF_PAGEOBJ_TEXT/CPDF_PageObject::TEXT mismatch"); +static_assert(FPDF_PAGEOBJ_PATH == CPDF_PageObject::PATH, + "FPDF_PAGEOBJ_PATH/CPDF_PageObject::PATH mismatch"); +static_assert(FPDF_PAGEOBJ_IMAGE == CPDF_PageObject::IMAGE, + "FPDF_PAGEOBJ_IMAGE/CPDF_PageObject::IMAGE mismatch"); +static_assert(FPDF_PAGEOBJ_SHADING == CPDF_PageObject::SHADING, + "FPDF_PAGEOBJ_SHADING/CPDF_PageObject::SHADING mismatch"); +static_assert(FPDF_PAGEOBJ_FORM == CPDF_PageObject::FORM, + "FPDF_PAGEOBJ_FORM/CPDF_PageObject::FORM mismatch"); + +const CPDF_ContentMarkItem* CPDFContentMarkItemFromFPDFPageObjectMark( + FPDF_PAGEOBJECTMARK mark) { + return static_cast(mark); +} + +bool IsPageObject(CPDF_Page* pPage) { + if (!pPage || !pPage->m_pFormDict || !pPage->m_pFormDict->KeyExist("Type")) + return false; + + CPDF_Object* pObject = pPage->m_pFormDict->GetObjectFor("Type")->GetDirect(); + return pObject && !pObject->GetString().Compare("Page"); +} + +void CalcBoundingBox(CPDF_PageObject* pPageObj) { + switch (pPageObj->GetType()) { + case CPDF_PageObject::TEXT: { + break; + } + case CPDF_PageObject::PATH: { + CPDF_PathObject* pPathObj = pPageObj->AsPath(); + pPathObj->CalcBoundingBox(); + break; + } + case CPDF_PageObject::IMAGE: { + CPDF_ImageObject* pImageObj = pPageObj->AsImage(); + pImageObj->CalcBoundingBox(); + break; + } + case CPDF_PageObject::SHADING: { + CPDF_ShadingObject* pShadingObj = pPageObj->AsShading(); + pShadingObj->CalcBoundingBox(); + break; + } + case CPDF_PageObject::FORM: { + CPDF_FormObject* pFormObj = pPageObj->AsForm(); + pFormObj->CalcBoundingBox(); + break; + } + default: { + NOTREACHED(); + break; + } + } +} + +} // namespace + +FPDF_EXPORT FPDF_DOCUMENT FPDF_CALLCONV FPDF_CreateNewDocument() { + auto pDoc = pdfium::MakeUnique(nullptr); + pDoc->CreateNewDoc(); + + time_t currentTime; + ByteString DateStr; + if (FSDK_IsSandBoxPolicyEnabled(FPDF_POLICY_MACHINETIME_ACCESS)) { + if (time(¤tTime) != -1) { + tm* pTM = localtime(¤tTime); + if (pTM) { + DateStr = ByteString::Format( + "D:%04d%02d%02d%02d%02d%02d", pTM->tm_year + 1900, pTM->tm_mon + 1, + pTM->tm_mday, pTM->tm_hour, pTM->tm_min, pTM->tm_sec); + } + } + } + + CPDF_Dictionary* pInfoDict = pDoc->GetInfo(); + if (pInfoDict) { + if (FSDK_IsSandBoxPolicyEnabled(FPDF_POLICY_MACHINETIME_ACCESS)) + pInfoDict->SetNewFor("CreationDate", DateStr, false); + pInfoDict->SetNewFor("Creator", L"PDFium"); + } + + // Caller takes ownership of pDoc. + return FPDFDocumentFromCPDFDocument(pDoc.release()); +} + +FPDF_EXPORT void FPDF_CALLCONV FPDFPage_Delete(FPDF_DOCUMENT document, + int page_index) { + if (UnderlyingDocumentType* pDoc = UnderlyingFromFPDFDocument(document)) + pDoc->DeletePage(page_index); +} + +FPDF_EXPORT FPDF_PAGE FPDF_CALLCONV FPDFPage_New(FPDF_DOCUMENT document, + int page_index, + double width, + double height) { + CPDF_Document* pDoc = CPDFDocumentFromFPDFDocument(document); + if (!pDoc) + return nullptr; + + page_index = pdfium::clamp(page_index, 0, pDoc->GetPageCount()); + CPDF_Dictionary* pPageDict = pDoc->CreateNewPage(page_index); + if (!pPageDict) + return nullptr; + + pPageDict->SetRectFor("MediaBox", CFX_FloatRect(0, 0, width, height)); + pPageDict->SetNewFor("Rotate", 0); + pPageDict->SetNewFor("Resources"); + +#ifdef PDF_ENABLE_XFA + auto pXFAPage = pdfium::MakeRetain( + static_cast(document), page_index); + pXFAPage->LoadPDFPage(pPageDict); + return pXFAPage.Leak(); // Caller takes ownership. +#else // PDF_ENABLE_XFA + auto pPage = pdfium::MakeUnique(pDoc, pPageDict, true); + pPage->ParseContent(); + return pPage.release(); // Caller takes ownership. +#endif // PDF_ENABLE_XFA +} + +FPDF_EXPORT int FPDF_CALLCONV FPDFPage_GetRotation(FPDF_PAGE page) { + CPDF_Page* pPage = CPDFPageFromFPDFPage(page); + return IsPageObject(pPage) ? pPage->GetPageRotation() : -1; +} + +FPDF_EXPORT void FPDF_CALLCONV FPDFPage_InsertObject(FPDF_PAGE page, + FPDF_PAGEOBJECT page_obj) { + CPDF_PageObject* pPageObj = CPDFPageObjectFromFPDFPageObject(page_obj); + if (!pPageObj) + return; + + std::unique_ptr pPageObjHolder(pPageObj); + CPDF_Page* pPage = CPDFPageFromFPDFPage(page); + if (!IsPageObject(pPage)) + return; + pPageObj->SetDirty(true); + + pPage->AppendPageObject(std::move(pPageObjHolder)); + CalcBoundingBox(pPageObj); +} + +FPDF_EXPORT FPDF_BOOL FPDF_CALLCONV +FPDFPage_RemoveObject(FPDF_PAGE page, FPDF_PAGEOBJECT page_obj) { + CPDF_PageObject* pPageObj = CPDFPageObjectFromFPDFPageObject(page_obj); + if (!pPageObj) + return false; + + CPDF_Page* pPage = CPDFPageFromFPDFPage(page); + if (!IsPageObject(pPage)) + return false; + + return pPage->RemovePageObject(pPageObj); +} + +FPDF_EXPORT int FPDF_CALLCONV FPDFPage_CountObject(FPDF_PAGE page) { + return FPDFPage_CountObjects(page); +} + +FPDF_EXPORT int FPDF_CALLCONV FPDFPage_CountObjects(FPDF_PAGE page) { + CPDF_Page* pPage = CPDFPageFromFPDFPage(page); + if (!IsPageObject(pPage)) + return -1; + + return pPage->GetPageObjectCount(); +} + +FPDF_EXPORT FPDF_PAGEOBJECT FPDF_CALLCONV FPDFPage_GetObject(FPDF_PAGE page, + int index) { + CPDF_Page* pPage = CPDFPageFromFPDFPage(page); + if (!IsPageObject(pPage)) + return nullptr; + + return pPage->GetPageObjectByIndex(index); +} + +FPDF_EXPORT FPDF_BOOL FPDF_CALLCONV FPDFPage_HasTransparency(FPDF_PAGE page) { + CPDF_Page* pPage = CPDFPageFromFPDFPage(page); + return pPage && pPage->BackgroundAlphaNeeded(); +} + +FPDF_EXPORT void FPDF_CALLCONV FPDFPageObj_Destroy(FPDF_PAGEOBJECT page_obj) { + delete CPDFPageObjectFromFPDFPageObject(page_obj); +} + +FPDF_EXPORT int FPDF_CALLCONV +FPDFPageObj_CountMarks(FPDF_PAGEOBJECT page_object) { + if (!page_object) + return -1; + + const auto& mark = + CPDFPageObjectFromFPDFPageObject(page_object)->m_ContentMark; + return mark.HasRef() ? mark.CountItems() : 0; +} + +FPDF_EXPORT FPDF_PAGEOBJECTMARK FPDF_CALLCONV +FPDFPageObj_GetMark(FPDF_PAGEOBJECT page_object, unsigned long index) { + if (!page_object) + return nullptr; + + const auto& mark = + CPDFPageObjectFromFPDFPageObject(page_object)->m_ContentMark; + if (!mark.HasRef()) + return nullptr; + + if (index >= mark.CountItems()) + return nullptr; + + return static_cast(&mark.GetItem(index)); +} + +FPDF_EXPORT unsigned long FPDF_CALLCONV +FPDFPageObjMark_GetName(FPDF_PAGEOBJECTMARK mark, + void* buffer, + unsigned long buflen) { + if (!mark) + return 0; + + const CPDF_ContentMarkItem* pMarkItem = + CPDFContentMarkItemFromFPDFPageObjectMark(mark); + + return Utf16EncodeMaybeCopyAndReturnLength( + WideString::FromUTF8(pMarkItem->GetName().AsStringView()), buffer, + buflen); +} + +FPDF_EXPORT FPDF_BOOL FPDF_CALLCONV +FPDFPageObj_HasTransparency(FPDF_PAGEOBJECT pageObject) { + if (!pageObject) + return false; + + CPDF_PageObject* pPageObj = CPDFPageObjectFromFPDFPageObject(pageObject); + int blend_type = pPageObj->m_GeneralState.GetBlendType(); + if (blend_type != FXDIB_BLEND_NORMAL) + return true; + + CPDF_Dictionary* pSMaskDict = + ToDictionary(pPageObj->m_GeneralState.GetSoftMask()); + if (pSMaskDict) + return true; + + if (pPageObj->m_GeneralState.GetFillAlpha() != 1.0f) + return true; + + if (pPageObj->IsPath() && pPageObj->m_GeneralState.GetStrokeAlpha() != 1.0f) { + return true; + } + + if (pPageObj->IsForm()) { + const CPDF_Form* pForm = pPageObj->AsForm()->form(); + if (pForm) { + int trans = pForm->m_iTransparency; + if ((trans & PDFTRANS_ISOLATED) || (trans & PDFTRANS_GROUP)) + return true; + } + } + + return false; +} + +FPDF_EXPORT int FPDF_CALLCONV FPDFPageObj_GetType(FPDF_PAGEOBJECT pageObject) { + if (!pageObject) + return FPDF_PAGEOBJ_UNKNOWN; + + CPDF_PageObject* pPageObj = CPDFPageObjectFromFPDFPageObject(pageObject); + return pPageObj->GetType(); +} + +FPDF_EXPORT FPDF_BOOL FPDF_CALLCONV FPDFPage_GenerateContent(FPDF_PAGE page) { + CPDF_Page* pPage = CPDFPageFromFPDFPage(page); + if (!IsPageObject(pPage)) + return false; + + CPDF_PageContentGenerator CG(pPage); + CG.GenerateContent(); + return true; +} + +FPDF_EXPORT void FPDF_CALLCONV +FPDFPageObj_Transform(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); + pPageObj->Transform(matrix); +} + +FPDF_EXPORT void FPDF_CALLCONV +FPDFPageObj_SetBlendMode(FPDF_PAGEOBJECT page_object, + FPDF_BYTESTRING blend_mode) { + CPDF_PageObject* pPageObj = CPDFPageObjectFromFPDFPageObject(page_object); + if (!pPageObj) + return; + + pPageObj->m_GeneralState.SetBlendMode(blend_mode); + pPageObj->SetDirty(true); +} + +FPDF_EXPORT void FPDF_CALLCONV FPDFPage_TransformAnnots(FPDF_PAGE page, + double a, + double b, + double c, + double d, + double e, + double f) { + CPDF_Page* pPage = CPDFPageFromFPDFPage(page); + if (!pPage) + return; + + CPDF_AnnotList AnnotList(pPage); + for (size_t i = 0; i < AnnotList.Count(); ++i) { + CPDF_Annot* pAnnot = AnnotList.GetAt(i); + CFX_Matrix matrix((float)a, (float)b, (float)c, (float)d, (float)e, + (float)f); + CFX_FloatRect rect = matrix.TransformRect(pAnnot->GetRect()); + + CPDF_Dictionary* pAnnotDict = pAnnot->GetAnnotDict(); + CPDF_Array* pRectArray = pAnnotDict->GetArrayFor("Rect"); + if (pRectArray) + pRectArray->Clear(); + else + pRectArray = pAnnotDict->SetNewFor("Rect"); + + pRectArray->AddNew(rect.left); + pRectArray->AddNew(rect.bottom); + pRectArray->AddNew(rect.right); + pRectArray->AddNew(rect.top); + + // TODO(unknown): Transform AP's rectangle + } +} + +FPDF_EXPORT void FPDF_CALLCONV FPDFPage_SetRotation(FPDF_PAGE page, + int rotate) { + CPDF_Page* pPage = CPDFPageFromFPDFPage(page); + if (!IsPageObject(pPage)) + return; + + rotate %= 4; + pPage->m_pFormDict->SetNewFor("Rotate", rotate * 90); +} + +FPDF_BOOL FPDFPageObj_SetFillColor(FPDF_PAGEOBJECT page_object, + unsigned int R, + unsigned int G, + unsigned int B, + unsigned int A) { + if (!page_object || R > 255 || G > 255 || B > 255 || A > 255) + return false; + + float rgb[3] = {R / 255.f, G / 255.f, B / 255.f}; + auto* pPageObj = CPDFPageObjectFromFPDFPageObject(page_object); + pPageObj->m_GeneralState.SetFillAlpha(A / 255.f); + pPageObj->m_ColorState.SetFillColor( + CPDF_ColorSpace::GetStockCS(PDFCS_DEVICERGB), rgb, 3); + pPageObj->SetDirty(true); + return true; +} + +FPDF_EXPORT FPDF_BOOL FPDF_CALLCONV +FPDFPageObj_GetBounds(FPDF_PAGEOBJECT pageObject, + float* left, + float* bottom, + float* right, + float* top) { + if (!pageObject) + return false; + + CPDF_PageObject* pPageObj = CPDFPageObjectFromFPDFPageObject(pageObject); + CFX_FloatRect bbox = pPageObj->GetRect(); + *left = bbox.left; + *bottom = bbox.bottom; + *right = bbox.right; + *top = bbox.top; + return true; +} diff --git a/fpdfsdk/fpdf_editpath.cpp b/fpdfsdk/fpdf_editpath.cpp new file mode 100644 index 0000000000..2daec67e36 --- /dev/null +++ b/fpdfsdk/fpdf_editpath.cpp @@ -0,0 +1,292 @@ +// Copyright 2017 PDFium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "public/fpdf_edit.h" + +#include + +#include "core/fpdfapi/page/cpdf_path.h" +#include "core/fpdfapi/page/cpdf_pathobject.h" +#include "core/fxcrt/fx_system.h" +#include "fpdfsdk/cpdfsdk_helpers.h" +#include "third_party/base/ptr_util.h" + +// These checks are here because core/ and public/ cannot depend on each other. +static_assert(CFX_GraphStateData::LineCapButt == FPDF_LINECAP_BUTT, + "CFX_GraphStateData::LineCapButt value mismatch"); +static_assert(CFX_GraphStateData::LineCapRound == FPDF_LINECAP_ROUND, + "CFX_GraphStateData::LineCapRound value mismatch"); +static_assert(CFX_GraphStateData::LineCapSquare == + FPDF_LINECAP_PROJECTING_SQUARE, + "CFX_GraphStateData::LineCapSquare value mismatch"); + +static_assert(CFX_GraphStateData::LineJoinMiter == FPDF_LINEJOIN_MITER, + "CFX_GraphStateData::LineJoinMiter value mismatch"); +static_assert(CFX_GraphStateData::LineJoinRound == FPDF_LINEJOIN_ROUND, + "CFX_GraphStateData::LineJoinRound value mismatch"); +static_assert(CFX_GraphStateData::LineJoinBevel == FPDF_LINEJOIN_BEVEL, + "CFX_GraphStateData::LineJoinBevel value mismatch"); + +static_assert(static_cast(FXPT_TYPE::LineTo) == FPDF_SEGMENT_LINETO, + "FXPT_TYPE::LineTo value mismatch"); +static_assert(static_cast(FXPT_TYPE::BezierTo) == FPDF_SEGMENT_BEZIERTO, + "FXPT_TYPE::BezierTo value mismatch"); +static_assert(static_cast(FXPT_TYPE::MoveTo) == FPDF_SEGMENT_MOVETO, + "FXPT_TYPE::MoveTo value mismatch"); + +namespace { + +CPDF_PathObject* CPDFPathObjectFromFPDFPageObject(FPDF_PAGEOBJECT page_object) { + auto* obj = CPDFPageObjectFromFPDFPageObject(page_object); + return obj ? obj->AsPath() : nullptr; +} + +const FX_PATHPOINT* FXPathPointFromFPDFPathSegment(FPDF_PATHSEGMENT segment) { + return static_cast(segment); +} + +unsigned int GetAlphaAsUnsignedInt(float alpha) { + return static_cast(alpha * 255.f + 0.5f); +} + +} // namespace + +FPDF_EXPORT FPDF_PAGEOBJECT FPDF_CALLCONV FPDFPageObj_CreateNewPath(float x, + float y) { + auto pPathObj = pdfium::MakeUnique(); + pPathObj->m_Path.AppendPoint(CFX_PointF(x, y), FXPT_TYPE::MoveTo, false); + pPathObj->DefaultStates(); + return pPathObj.release(); // Caller takes ownership. +} + +FPDF_EXPORT FPDF_PAGEOBJECT FPDF_CALLCONV FPDFPageObj_CreateNewRect(float x, + float y, + float w, + float h) { + auto pPathObj = pdfium::MakeUnique(); + pPathObj->m_Path.AppendRect(x, y, x + w, y + h); + pPathObj->DefaultStates(); + return pPathObj.release(); // Caller takes ownership. +} + +FPDF_EXPORT FPDF_BOOL FPDF_CALLCONV +FPDFPath_SetStrokeColor(FPDF_PAGEOBJECT path, + unsigned int R, + unsigned int G, + unsigned int B, + unsigned int A) { + auto* pPathObj = CPDFPathObjectFromFPDFPageObject(path); + if (!pPathObj || R > 255 || G > 255 || B > 255 || A > 255) + return false; + + float rgb[3] = {R / 255.f, G / 255.f, B / 255.f}; + pPathObj->m_GeneralState.SetStrokeAlpha(A / 255.f); + pPathObj->m_ColorState.SetStrokeColor( + CPDF_ColorSpace::GetStockCS(PDFCS_DEVICERGB), rgb, 3); + pPathObj->SetDirty(true); + return true; +} + +FPDF_EXPORT FPDF_BOOL FPDF_CALLCONV +FPDFPath_GetStrokeColor(FPDF_PAGEOBJECT path, + unsigned int* R, + unsigned int* G, + unsigned int* B, + unsigned int* A) { + auto* pPathObj = CPDFPathObjectFromFPDFPageObject(path); + if (!pPathObj || !R || !G || !B || !A) + return false; + + FX_COLORREF strokeColor = pPathObj->m_ColorState.GetStrokeColorRef(); + *R = FXSYS_GetRValue(strokeColor); + *G = FXSYS_GetGValue(strokeColor); + *B = FXSYS_GetBValue(strokeColor); + *A = GetAlphaAsUnsignedInt(pPathObj->m_GeneralState.GetStrokeAlpha()); + return true; +} + +FPDF_EXPORT FPDF_BOOL FPDF_CALLCONV +FPDFPath_SetStrokeWidth(FPDF_PAGEOBJECT path, float width) { + auto* pPathObj = CPDFPathObjectFromFPDFPageObject(path); + if (!pPathObj || width < 0.0f) + return false; + + pPathObj->m_GraphState.SetLineWidth(width); + pPathObj->SetDirty(true); + return true; +} + +FPDF_EXPORT FPDF_BOOL FPDF_CALLCONV FPDFPath_SetFillColor(FPDF_PAGEOBJECT path, + unsigned int R, + unsigned int G, + unsigned int B, + unsigned int A) { + return FPDFPageObj_SetFillColor(path, R, G, B, A); +} + +FPDF_EXPORT FPDF_BOOL FPDF_CALLCONV FPDFPath_GetFillColor(FPDF_PAGEOBJECT path, + unsigned int* R, + unsigned int* G, + unsigned int* B, + unsigned int* A) { + auto* pPathObj = CPDFPathObjectFromFPDFPageObject(path); + if (!pPathObj || !R || !G || !B || !A) + return false; + + FX_COLORREF fillColor = pPathObj->m_ColorState.GetFillColorRef(); + *R = FXSYS_GetRValue(fillColor); + *G = FXSYS_GetGValue(fillColor); + *B = FXSYS_GetBValue(fillColor); + *A = GetAlphaAsUnsignedInt(pPathObj->m_GeneralState.GetFillAlpha()); + return true; +} + +FPDF_EXPORT int FPDF_CALLCONV FPDFPath_CountSegments(FPDF_PAGEOBJECT path) { + auto* pPathObj = CPDFPathObjectFromFPDFPageObject(path); + if (!pPathObj) + return -1; + return pdfium::CollectionSize(pPathObj->m_Path.GetPoints()); +} + +FPDF_EXPORT FPDF_PATHSEGMENT FPDF_CALLCONV +FPDFPath_GetPathSegment(FPDF_PAGEOBJECT path, int index) { + auto* pPathObj = CPDFPathObjectFromFPDFPageObject(path); + if (!pPathObj) + return nullptr; + + const std::vector& points = pPathObj->m_Path.GetPoints(); + return pdfium::IndexInBounds(points, index) ? &points[index] : nullptr; +} + +FPDF_EXPORT FPDF_BOOL FPDF_CALLCONV FPDFPath_MoveTo(FPDF_PAGEOBJECT path, + float x, + float y) { + auto* pPathObj = CPDFPathObjectFromFPDFPageObject(path); + if (!pPathObj) + return false; + + pPathObj->m_Path.AppendPoint(CFX_PointF(x, y), FXPT_TYPE::MoveTo, false); + pPathObj->SetDirty(true); + return true; +} + +FPDF_EXPORT FPDF_BOOL FPDF_CALLCONV FPDFPath_LineTo(FPDF_PAGEOBJECT path, + float x, + float y) { + auto* pPathObj = CPDFPathObjectFromFPDFPageObject(path); + if (!pPathObj) + return false; + + pPathObj->m_Path.AppendPoint(CFX_PointF(x, y), FXPT_TYPE::LineTo, false); + pPathObj->SetDirty(true); + return true; +} + +FPDF_EXPORT FPDF_BOOL FPDF_CALLCONV FPDFPath_BezierTo(FPDF_PAGEOBJECT path, + float x1, + float y1, + float x2, + float y2, + float x3, + float y3) { + auto* pPathObj = CPDFPathObjectFromFPDFPageObject(path); + if (!pPathObj) + return false; + + pPathObj->m_Path.AppendPoint(CFX_PointF(x1, y1), FXPT_TYPE::BezierTo, false); + pPathObj->m_Path.AppendPoint(CFX_PointF(x2, y2), FXPT_TYPE::BezierTo, false); + pPathObj->m_Path.AppendPoint(CFX_PointF(x3, y3), FXPT_TYPE::BezierTo, false); + pPathObj->SetDirty(true); + return true; +} + +FPDF_EXPORT FPDF_BOOL FPDF_CALLCONV FPDFPath_Close(FPDF_PAGEOBJECT path) { + auto* pPathObj = CPDFPathObjectFromFPDFPageObject(path); + if (!pPathObj) + return false; + + if (pPathObj->m_Path.GetPoints().empty()) + return false; + + pPathObj->m_Path.ClosePath(); + pPathObj->SetDirty(true); + return true; +} + +FPDF_EXPORT FPDF_BOOL FPDF_CALLCONV FPDFPath_SetDrawMode(FPDF_PAGEOBJECT path, + int fillmode, + FPDF_BOOL stroke) { + auto* pPathObj = CPDFPathObjectFromFPDFPageObject(path); + if (!pPathObj) + return false; + + if (fillmode == FPDF_FILLMODE_ALTERNATE) + pPathObj->m_FillType = FXFILL_ALTERNATE; + else if (fillmode == FPDF_FILLMODE_WINDING) + pPathObj->m_FillType = FXFILL_WINDING; + else + pPathObj->m_FillType = 0; + pPathObj->m_bStroke = stroke != 0; + pPathObj->SetDirty(true); + return true; +} + +FPDF_EXPORT void FPDF_CALLCONV FPDFPath_SetLineJoin(FPDF_PAGEOBJECT path, + int line_join) { + if (!path) + return; + if (line_join < + static_cast(CFX_GraphStateData::LineJoin::LineJoinMiter) || + line_join > + static_cast(CFX_GraphStateData::LineJoin::LineJoinBevel)) { + return; + } + auto* pPathObj = CPDFPageObjectFromFPDFPageObject(path); + CFX_GraphStateData::LineJoin lineJoin = + static_cast(line_join); + pPathObj->m_GraphState.SetLineJoin(lineJoin); + pPathObj->SetDirty(true); +} + +FPDF_EXPORT void FPDF_CALLCONV FPDFPath_SetLineCap(FPDF_PAGEOBJECT path, + int line_cap) { + if (!path) + return; + if (line_cap < static_cast(CFX_GraphStateData::LineCap::LineCapButt) || + line_cap > static_cast(CFX_GraphStateData::LineCap::LineCapSquare)) { + return; + } + auto* pPathObj = CPDFPageObjectFromFPDFPageObject(path); + CFX_GraphStateData::LineCap lineCap = + static_cast(line_cap); + pPathObj->m_GraphState.SetLineCap(lineCap); + pPathObj->SetDirty(true); +} + +FPDF_EXPORT FPDF_BOOL FPDF_CALLCONV +FPDFPathSegment_GetPoint(FPDF_PATHSEGMENT segment, float* x, float* y) { + auto* pPathPoint = FXPathPointFromFPDFPathSegment(segment); + if (!pPathPoint || !x || !y) + return false; + + *x = pPathPoint->m_Point.x; + *y = pPathPoint->m_Point.y; + + return true; +} + +FPDF_EXPORT int FPDF_CALLCONV +FPDFPathSegment_GetType(FPDF_PATHSEGMENT segment) { + auto* pPathPoint = FXPathPointFromFPDFPathSegment(segment); + + return pPathPoint ? static_cast(pPathPoint->m_Type) + : FPDF_SEGMENT_UNKNOWN; +} + +FPDF_EXPORT FPDF_BOOL FPDF_CALLCONV +FPDFPathSegment_GetClose(FPDF_PATHSEGMENT segment) { + auto* pPathPoint = FXPathPointFromFPDFPathSegment(segment); + + return pPathPoint ? pPathPoint->m_CloseFigure : false; +} diff --git a/fpdfsdk/fpdf_editpath_embeddertest.cpp b/fpdfsdk/fpdf_editpath_embeddertest.cpp new file mode 100644 index 0000000000..59e5dbbabf --- /dev/null +++ b/fpdfsdk/fpdf_editpath_embeddertest.cpp @@ -0,0 +1,63 @@ +// Copyright 2017 PDFium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "core/fxcrt/fx_system.h" +#include "public/fpdf_edit.h" +#include "testing/embedder_test.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "testing/test_support.h" + +class FPDFEditPathEmbedderTest : public EmbedderTest {}; + +TEST_F(FPDFEditPathEmbedderTest, VerifyCorrectColoursReturned) { + CreateEmptyDocument(); + FPDF_PAGE page = FPDFPage_New(document(), 0, 612, 792); + + for (size_t i = 0; i < 256; ++i) { + FPDF_PAGEOBJECT path = FPDFPageObj_CreateNewPath(400, 100); + EXPECT_TRUE(FPDFPath_SetFillColor(path, i, i, i, i)); + EXPECT_TRUE(FPDFPath_SetStrokeColor(path, i, i, i, i)); + EXPECT_TRUE(FPDFPath_SetDrawMode(path, FPDF_FILLMODE_ALTERNATE, 0)); + EXPECT_TRUE(FPDFPath_LineTo(path, 400, 200)); + EXPECT_TRUE(FPDFPath_LineTo(path, 300, 100)); + EXPECT_TRUE(FPDFPath_Close(path)); + + FPDFPage_InsertObject(page, path); + } + + EXPECT_TRUE(FPDFPage_GenerateContent(page)); + EXPECT_TRUE(FPDF_SaveAsCopy(document(), this, 0)); + FPDF_ClosePage(page); + page = nullptr; + + OpenSavedDocument(); + page = LoadSavedPage(0); + ASSERT(page); + + for (size_t i = 0; i < 256; ++i) { + FPDF_PAGEOBJECT path = FPDFPage_GetObject(page, i); + ASSERT(path); + + EXPECT_EQ(FPDF_PAGEOBJ_PATH, FPDFPageObj_GetType(path)); + + unsigned int r; + unsigned int g; + unsigned int b; + unsigned int a; + FPDFPath_GetFillColor(path, &r, &g, &b, &a); + EXPECT_EQ(i, r); + EXPECT_EQ(i, g); + EXPECT_EQ(i, b); + EXPECT_EQ(i, a); + + FPDFPath_GetStrokeColor(path, &r, &g, &b, &a); + EXPECT_EQ(i, r); + EXPECT_EQ(i, g); + EXPECT_EQ(i, b); + EXPECT_EQ(i, a); + } + + CloseSavedPage(page); + CloseSavedDocument(); +} diff --git a/fpdfsdk/fpdf_edittext.cpp b/fpdfsdk/fpdf_edittext.cpp new file mode 100644 index 0000000000..8155003a1f --- /dev/null +++ b/fpdfsdk/fpdf_edittext.cpp @@ -0,0 +1,499 @@ +// Copyright 2017 PDFium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include +#include +#include +#include +#include +#include + +#include "core/fpdfapi/cpdf_modulemgr.h" +#include "core/fpdfapi/font/cpdf_font.h" +#include "core/fpdfapi/font/cpdf_type1font.h" +#include "core/fpdfapi/page/cpdf_docpagedata.h" +#include "core/fpdfapi/page/cpdf_textobject.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_name.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_extension.h" +#include "core/fxge/cfx_fontmgr.h" +#include "core/fxge/fx_font.h" +#include "fpdfsdk/cpdfsdk_helpers.h" +#include "public/fpdf_edit.h" + +namespace { + +CPDF_Dictionary* LoadFontDesc(CPDF_Document* pDoc, + const ByteString& font_name, + CFX_Font* pFont, + const uint8_t* data, + uint32_t size, + int font_type) { + CPDF_Dictionary* pFontDesc = pDoc->NewIndirect(); + pFontDesc->SetNewFor("Type", "FontDescriptor"); + pFontDesc->SetNewFor("FontName", font_name); + int flags = 0; + if (FXFT_Is_Face_fixedwidth(pFont->GetFace())) + flags |= FXFONT_FIXED_PITCH; + if (font_name.Contains("Serif")) + flags |= FXFONT_SERIF; + if (FXFT_Is_Face_Italic(pFont->GetFace())) + flags |= FXFONT_ITALIC; + if (FXFT_Is_Face_Bold(pFont->GetFace())) + flags |= FXFONT_BOLD; + + // TODO(npm): How do I know if a font is symbolic, script, allcap, smallcap + flags |= FXFONT_NONSYMBOLIC; + + pFontDesc->SetNewFor("Flags", flags); + FX_RECT bbox; + pFont->GetBBox(bbox); + pFontDesc->SetRectFor("FontBBox", CFX_FloatRect(bbox)); + + // TODO(npm): calculate italic angle correctly + pFontDesc->SetNewFor("ItalicAngle", pFont->IsItalic() ? -12 : 0); + + pFontDesc->SetNewFor("Ascent", pFont->GetAscent()); + pFontDesc->SetNewFor("Descent", pFont->GetDescent()); + + // TODO(npm): calculate the capheight, stemV correctly + pFontDesc->SetNewFor("CapHeight", pFont->GetAscent()); + pFontDesc->SetNewFor("StemV", pFont->IsBold() ? 120 : 70); + + CPDF_Stream* pStream = pDoc->NewIndirect(); + pStream->SetData(data, size); + // TODO(npm): Lengths for Type1 fonts. + if (font_type == FPDF_FONT_TRUETYPE) { + pStream->GetDict()->SetNewFor("Length1", + static_cast(size)); + } + ByteString fontFile = font_type == FPDF_FONT_TYPE1 ? "FontFile" : "FontFile2"; + pFontDesc->SetNewFor(fontFile, pDoc, pStream->GetObjNum()); + return pFontDesc; +} + +const char ToUnicodeStart[] = + "/CIDInit /ProcSet findresource begin\n" + "12 dict begin\n" + "begincmap\n" + "/CIDSystemInfo\n" + "<> def\n" + "/CMapName /Adobe-Identity-H def\n" + "CMapType 2 def\n" + "1 begincodespacerange\n" + "<0000> \n" + "endcodespacerange\n"; + +const char ToUnicodeEnd[] = + "endcmap\n" + "CMapName currentdict /CMap defineresource pop\n" + "end\n" + "end\n"; + +void AddCharcode(std::ostringstream* pBuffer, uint32_t number) { + ASSERT(number <= 0xFFFF); + *pBuffer << "<"; + char ans[4]; + FXSYS_IntToFourHexChars(number, ans); + for (size_t i = 0; i < 4; ++i) + *pBuffer << ans[i]; + *pBuffer << ">"; +} + +// PDF spec 1.7 Section 5.9.2: "Unicode character sequences as expressed in +// UTF-16BE encoding." See https://en.wikipedia.org/wiki/UTF-16#Description +void AddUnicode(std::ostringstream* pBuffer, uint32_t unicode) { + if (unicode >= 0xD800 && unicode <= 0xDFFF) + unicode = 0; + + char ans[8]; + *pBuffer << "<"; + size_t numChars = FXSYS_ToUTF16BE(unicode, ans); + for (size_t i = 0; i < numChars; ++i) + *pBuffer << ans[i]; + *pBuffer << ">"; +} + +// Loads the charcode to unicode mapping into a stream +CPDF_Stream* LoadUnicode(CPDF_Document* pDoc, + const std::map& to_unicode) { + // A map charcode->unicode + std::map char_to_uni; + // A map to vector v of unicode characters of size (end + // - start + 1). This abbreviates: start->v[0], start+1->v[1], etc. PDF spec + // 1.7 Section 5.9.2 says that only the last byte of the unicode may change. + std::map, std::vector> + map_range_vector; + // A map -> unicode + // This abbreviates: start->unicode, start+1->unicode+1, etc. + // PDF spec 1.7 Section 5.9.2 says that only the last byte of the unicode may + // change. + std::map, uint32_t> map_range; + + // Calculate the maps + for (auto iter = to_unicode.begin(); iter != to_unicode.end(); ++iter) { + uint32_t firstCharcode = iter->first; + uint32_t firstUnicode = iter->second; + if (std::next(iter) == to_unicode.end() || + firstCharcode + 1 != std::next(iter)->first) { + char_to_uni[firstCharcode] = firstUnicode; + continue; + } + ++iter; + uint32_t curCharcode = iter->first; + uint32_t curUnicode = iter->second; + if (curCharcode % 256 == 0) { + char_to_uni[firstCharcode] = firstUnicode; + char_to_uni[curCharcode] = curUnicode; + continue; + } + const size_t maxExtra = 255 - (curCharcode % 256); + auto next_it = std::next(iter); + if (firstUnicode + 1 != curUnicode) { + // Consecutive charcodes mapping to non-consecutive unicodes + std::vector unicodes; + unicodes.push_back(firstUnicode); + unicodes.push_back(curUnicode); + for (size_t i = 0; i < maxExtra; ++i) { + if (next_it == to_unicode.end() || curCharcode + 1 != next_it->first) + break; + ++iter; + ++curCharcode; + unicodes.push_back(iter->second); + next_it = std::next(iter); + } + ASSERT(iter->first - firstCharcode + 1 == unicodes.size()); + map_range_vector[std::make_pair(firstCharcode, iter->first)] = unicodes; + continue; + } + // Consecutive charcodes mapping to consecutive unicodes + for (size_t i = 0; i < maxExtra; ++i) { + if (next_it == to_unicode.end() || curCharcode + 1 != next_it->first || + curUnicode + 1 != next_it->second) { + break; + } + ++iter; + ++curCharcode; + ++curUnicode; + next_it = std::next(iter); + } + map_range[std::make_pair(firstCharcode, curCharcode)] = firstUnicode; + } + std::ostringstream buffer; + buffer << ToUnicodeStart; + // Add maps to buffer + buffer << static_cast(char_to_uni.size()) << " beginbfchar\n"; + for (const auto& iter : char_to_uni) { + AddCharcode(&buffer, iter.first); + buffer << " "; + AddUnicode(&buffer, iter.second); + buffer << "\n"; + } + buffer << "endbfchar\n" + << static_cast(map_range_vector.size() + map_range.size()) + << " beginbfrange\n"; + for (const auto& iter : map_range_vector) { + const std::pair& charcodeRange = iter.first; + AddCharcode(&buffer, charcodeRange.first); + buffer << " "; + AddCharcode(&buffer, charcodeRange.second); + buffer << " ["; + const std::vector& unicodes = iter.second; + for (size_t i = 0; i < unicodes.size(); ++i) { + uint32_t uni = unicodes[i]; + AddUnicode(&buffer, uni); + if (i != unicodes.size() - 1) + buffer << " "; + } + buffer << "]\n"; + } + for (const auto& iter : map_range) { + const std::pair& charcodeRange = iter.first; + AddCharcode(&buffer, charcodeRange.first); + buffer << " "; + AddCharcode(&buffer, charcodeRange.second); + buffer << " "; + AddUnicode(&buffer, iter.second); + buffer << "\n"; + } + buffer << "endbfrange\n"; + buffer << ToUnicodeEnd; + // TODO(npm): Encrypt / Compress? + CPDF_Stream* stream = pDoc->NewIndirect(); + stream->SetData(&buffer); + return stream; +} + +const uint32_t kMaxSimpleFontChar = 0xFF; + +void* LoadSimpleFont(CPDF_Document* pDoc, + std::unique_ptr pFont, + const uint8_t* data, + uint32_t size, + int font_type) { + CPDF_Dictionary* fontDict = pDoc->NewIndirect(); + fontDict->SetNewFor("Type", "Font"); + fontDict->SetNewFor( + "Subtype", font_type == FPDF_FONT_TYPE1 ? "Type1" : "TrueType"); + ByteString name = pFont->GetFaceName(); + if (name.IsEmpty()) + name = "Unnamed"; + fontDict->SetNewFor("BaseFont", name); + + uint32_t glyphIndex; + uint32_t currentChar = FXFT_Get_First_Char(pFont->GetFace(), &glyphIndex); + if (currentChar > kMaxSimpleFontChar || glyphIndex == 0) + return nullptr; + fontDict->SetNewFor("FirstChar", static_cast(currentChar)); + CPDF_Array* widthsArray = pDoc->NewIndirect(); + while (true) { + uint32_t width = + std::min(pFont->GetGlyphWidth(glyphIndex), + static_cast(std::numeric_limits::max())); + widthsArray->AddNew(static_cast(width)); + uint32_t nextChar = + FXFT_Get_Next_Char(pFont->GetFace(), currentChar, &glyphIndex); + // Simple fonts have 1-byte charcodes only. + if (nextChar > kMaxSimpleFontChar || glyphIndex == 0) + break; + for (uint32_t i = currentChar + 1; i < nextChar; i++) + widthsArray->AddNew(0); + currentChar = nextChar; + } + fontDict->SetNewFor("LastChar", static_cast(currentChar)); + fontDict->SetNewFor("Widths", pDoc, widthsArray->GetObjNum()); + CPDF_Dictionary* pFontDesc = + LoadFontDesc(pDoc, name, pFont.get(), data, size, font_type); + + fontDict->SetNewFor("FontDescriptor", pDoc, + pFontDesc->GetObjNum()); + return pDoc->LoadFont(fontDict); +} + +const uint32_t kMaxUnicode = 0x10FFFF; + +void* LoadCompositeFont(CPDF_Document* pDoc, + std::unique_ptr pFont, + const uint8_t* data, + uint32_t size, + int font_type) { + CPDF_Dictionary* fontDict = pDoc->NewIndirect(); + fontDict->SetNewFor("Type", "Font"); + fontDict->SetNewFor("Subtype", "Type0"); + // TODO(npm): Get the correct encoding, if it's not identity. + ByteString encoding = "Identity-H"; + fontDict->SetNewFor("Encoding", encoding); + ByteString name = pFont->GetFaceName(); + if (name.IsEmpty()) + name = "Unnamed"; + fontDict->SetNewFor( + "BaseFont", font_type == FPDF_FONT_TYPE1 ? name + "-" + encoding : name); + + CPDF_Dictionary* pCIDFont = pDoc->NewIndirect(); + pCIDFont->SetNewFor("Type", "Font"); + pCIDFont->SetNewFor("Subtype", font_type == FPDF_FONT_TYPE1 + ? "CIDFontType0" + : "CIDFontType2"); + pCIDFont->SetNewFor("BaseFont", name); + + // TODO(npm): Maybe use FT_Get_CID_Registry_Ordering_Supplement to get the + // CIDSystemInfo + CPDF_Dictionary* pCIDSystemInfo = pDoc->NewIndirect(); + pCIDSystemInfo->SetNewFor("Registry", "Adobe"); + pCIDSystemInfo->SetNewFor("Ordering", "Identity"); + pCIDSystemInfo->SetNewFor("Supplement", 0); + pCIDFont->SetNewFor("CIDSystemInfo", pDoc, + pCIDSystemInfo->GetObjNum()); + + CPDF_Dictionary* pFontDesc = + LoadFontDesc(pDoc, name, pFont.get(), data, size, font_type); + pCIDFont->SetNewFor("FontDescriptor", pDoc, + pFontDesc->GetObjNum()); + + uint32_t glyphIndex; + uint32_t currentChar = FXFT_Get_First_Char(pFont->GetFace(), &glyphIndex); + // If it doesn't have a single char, just fail + if (glyphIndex == 0 || currentChar > kMaxUnicode) + return nullptr; + + std::map to_unicode; + std::map widths; + while (true) { + if (currentChar > kMaxUnicode) + break; + + widths[glyphIndex] = pFont->GetGlyphWidth(glyphIndex); + to_unicode[glyphIndex] = currentChar; + currentChar = + FXFT_Get_Next_Char(pFont->GetFace(), currentChar, &glyphIndex); + if (glyphIndex == 0) + break; + } + CPDF_Array* widthsArray = pDoc->NewIndirect(); + for (auto it = widths.begin(); it != widths.end(); ++it) { + int ch = it->first; + int w = it->second; + if (std::next(it) == widths.end()) { + // Only one char left, use format c [w] + auto oneW = pdfium::MakeUnique(); + oneW->AddNew(w); + widthsArray->AddNew(ch); + widthsArray->Add(std::move(oneW)); + break; + } + ++it; + int next_ch = it->first; + int next_w = it->second; + if (next_ch == ch + 1 && next_w == w) { + // The array can have a group c_first c_last w: all CIDs in the range from + // c_first to c_last will have width w + widthsArray->AddNew(ch); + ch = next_ch; + while (true) { + auto next_it = std::next(it); + if (next_it == widths.end() || next_it->first != it->first + 1 || + next_it->second != it->second) { + break; + } + ++it; + ch = it->first; + } + widthsArray->AddNew(ch); + widthsArray->AddNew(w); + continue; + } + // Otherwise we can have a group of the form c [w1 w2 ...]: c has width + // w1, c+1 has width w2, etc. + widthsArray->AddNew(ch); + auto curWidthArray = pdfium::MakeUnique(); + curWidthArray->AddNew(w); + curWidthArray->AddNew(next_w); + while (true) { + auto next_it = std::next(it); + if (next_it == widths.end() || next_it->first != it->first + 1) + break; + ++it; + curWidthArray->AddNew(static_cast(it->second)); + } + widthsArray->Add(std::move(curWidthArray)); + } + pCIDFont->SetNewFor("W", pDoc, widthsArray->GetObjNum()); + // TODO(npm): Support vertical writing + + auto pDescendant = pdfium::MakeUnique(); + pDescendant->AddNew(pDoc, pCIDFont->GetObjNum()); + fontDict->SetFor("DescendantFonts", std::move(pDescendant)); + CPDF_Stream* toUnicodeStream = LoadUnicode(pDoc, to_unicode); + fontDict->SetNewFor("ToUnicode", pDoc, + toUnicodeStream->GetObjNum()); + return pDoc->LoadFont(fontDict); +} + +} // namespace + +FPDF_EXPORT FPDF_PAGEOBJECT FPDF_CALLCONV +FPDFPageObj_NewTextObj(FPDF_DOCUMENT document, + FPDF_BYTESTRING font, + float font_size) { + CPDF_Document* pDoc = CPDFDocumentFromFPDFDocument(document); + if (!pDoc) + return nullptr; + + CPDF_Font* pFont = CPDF_Font::GetStockFont(pDoc, ByteStringView(font)); + if (!pFont) + return nullptr; + + auto pTextObj = pdfium::MakeUnique(); + pTextObj->m_TextState.SetFont(pFont); + pTextObj->m_TextState.SetFontSize(font_size); + pTextObj->DefaultStates(); + return pTextObj.release(); // Caller takes ownership. +} + +FPDF_EXPORT FPDF_BOOL FPDF_CALLCONV +FPDFText_SetText(FPDF_PAGEOBJECT text_object, FPDF_WIDESTRING text) { + auto* pTextObj = static_cast(text_object); + if (!pTextObj) + return false; + + size_t len = WideString::WStringLength(text); + WideString encodedText = WideString::FromUTF16LE(text, len); + ByteString byteText; + for (wchar_t wc : encodedText) { + pTextObj->GetFont()->AppendChar( + &byteText, pTextObj->GetFont()->CharCodeFromUnicode(wc)); + } + pTextObj->SetText(byteText); + return true; +} + +FPDF_EXPORT FPDF_FONT FPDF_CALLCONV FPDFText_LoadFont(FPDF_DOCUMENT document, + const uint8_t* data, + uint32_t size, + int font_type, + FPDF_BOOL cid) { + CPDF_Document* pDoc = CPDFDocumentFromFPDFDocument(document); + if (!pDoc || !data || size == 0 || + (font_type != FPDF_FONT_TYPE1 && font_type != FPDF_FONT_TRUETYPE)) { + return nullptr; + } + + auto pFont = pdfium::MakeUnique(); + + // TODO(npm): Maybe use FT_Get_X11_Font_Format to check format? Otherwise, we + // are allowing giving any font that can be loaded on freetype and setting it + // as any font type. + if (!pFont->LoadEmbedded(data, size)) + return nullptr; + + return cid ? LoadCompositeFont(pDoc, std::move(pFont), data, size, font_type) + : LoadSimpleFont(pDoc, std::move(pFont), data, size, font_type); +} + +FPDF_EXPORT FPDF_BOOL FPDF_CALLCONV +FPDFText_SetFillColor(FPDF_PAGEOBJECT text_object, + unsigned int R, + unsigned int G, + unsigned int B, + unsigned int A) { + return FPDFPageObj_SetFillColor(text_object, R, G, B, A); +} + +FPDF_EXPORT void FPDF_CALLCONV FPDFFont_Close(FPDF_FONT font) { + CPDF_Font* pFont = static_cast(font); + if (!pFont) + return; + + CPDF_Document* pDoc = pFont->GetDocument(); + if (!pDoc) + return; + + CPDF_DocPageData* pPageData = pDoc->GetPageData(); + if (!pPageData->IsForceClear()) + pPageData->ReleaseFont(pFont->GetFontDict()); +} + +FPDF_EXPORT FPDF_PAGEOBJECT FPDF_CALLCONV +FPDFPageObj_CreateTextObj(FPDF_DOCUMENT document, + FPDF_FONT font, + float font_size) { + CPDF_Document* pDoc = CPDFDocumentFromFPDFDocument(document); + CPDF_Font* pFont = static_cast(font); + if (!pDoc || !pFont) + return nullptr; + + auto pTextObj = pdfium::MakeUnique(); + pTextObj->m_TextState.SetFont(pDoc->LoadFont(pFont->GetFontDict())); + pTextObj->m_TextState.SetFontSize(font_size); + pTextObj->DefaultStates(); + return pTextObj.release(); +} diff --git a/fpdfsdk/fpdf_ext_embeddertest.cpp b/fpdfsdk/fpdf_ext_embeddertest.cpp new file mode 100644 index 0000000000..7c28c29a8e --- /dev/null +++ b/fpdfsdk/fpdf_ext_embeddertest.cpp @@ -0,0 +1,24 @@ +// Copyright 2016 PDFium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "public/fpdf_ext.h" +#include "public/fpdfview.h" +#include "testing/embedder_test.h" +#include "testing/gtest/include/gtest/gtest.h" + +class FPDFExtEmbeddertest : public EmbedderTest {}; + +TEST_F(FPDFExtEmbeddertest, PageModeUnknown) { + EXPECT_EQ(PAGEMODE_UNKNOWN, FPDFDoc_GetPageMode(nullptr)); +} + +TEST_F(FPDFExtEmbeddertest, PageModeUseNone) { + EXPECT_TRUE(OpenDocument("hello_world.pdf")); + EXPECT_EQ(PAGEMODE_USENONE, FPDFDoc_GetPageMode(document())); +} + +TEST_F(FPDFExtEmbeddertest, PageModeUseOutlines) { + EXPECT_TRUE(OpenDocument("use_outlines.pdf")); + EXPECT_EQ(PAGEMODE_USEOUTLINES, FPDFDoc_GetPageMode(document())); +} diff --git a/fpdfsdk/fpdf_formfill.cpp b/fpdfsdk/fpdf_formfill.cpp new file mode 100644 index 0000000000..ddd460da05 --- /dev/null +++ b/fpdfsdk/fpdf_formfill.cpp @@ -0,0 +1,836 @@ +// Copyright 2014 PDFium Authors. All rights reserved. +// 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_formfill.h" + +#include +#include + +#include "core/fpdfapi/page/cpdf_page.h" +#include "core/fpdfapi/parser/cpdf_document.h" +#include "core/fpdfapi/render/cpdf_renderoptions.h" +#include "core/fpdfdoc/cpdf_formcontrol.h" +#include "core/fpdfdoc/cpdf_formfield.h" +#include "core/fpdfdoc/cpdf_interform.h" +#include "core/fpdfdoc/cpdf_occontext.h" +#include "core/fxge/cfx_defaultrenderdevice.h" +#include "fpdfsdk/cpdfsdk_actionhandler.h" +#include "fpdfsdk/cpdfsdk_formfillenvironment.h" +#include "fpdfsdk/cpdfsdk_helpers.h" +#include "fpdfsdk/cpdfsdk_interform.h" +#include "fpdfsdk/cpdfsdk_pageview.h" +#include "public/fpdfview.h" +#include "third_party/base/ptr_util.h" +#include "third_party/base/stl_util.h" + +#ifdef PDF_ENABLE_XFA +#include "fpdfsdk/fpdfxfa/cpdfxfa_context.h" +#include "fpdfsdk/fpdfxfa/cpdfxfa_page.h" +#include "xfa/fxfa/cxfa_ffdocview.h" +#include "xfa/fxfa/cxfa_ffpageview.h" +#include "xfa/fxfa/cxfa_ffwidget.h" + +static_assert(static_cast(FormType::kNone) == FORMTYPE_NONE, + "None form types must match"); +static_assert(static_cast(FormType::kAcroForm) == FORMTYPE_ACRO_FORM, + "AcroForm form types must match"); +static_assert(static_cast(FormType::kXFAFull) == FORMTYPE_XFA_FULL, + "XFA full form types must match"); +static_assert(static_cast(FormType::kXFAForeground) == + FORMTYPE_XFA_FOREGROUND, + "XFA foreground form types must match"); +#endif // PDF_ENABLE_XFA + +static_assert(static_cast(FormFieldType::kUnknown) == + FPDF_FORMFIELD_UNKNOWN, + "Unknown form field types must match"); +static_assert(static_cast(FormFieldType::kPushButton) == + FPDF_FORMFIELD_PUSHBUTTON, + "PushButton form field types must match"); +static_assert(static_cast(FormFieldType::kCheckBox) == + FPDF_FORMFIELD_CHECKBOX, + "CheckBox form field types must match"); +static_assert(static_cast(FormFieldType::kRadioButton) == + FPDF_FORMFIELD_RADIOBUTTON, + "RadioButton form field types must match"); +static_assert(static_cast(FormFieldType::kComboBox) == + FPDF_FORMFIELD_COMBOBOX, + "ComboBox form field types must match"); +static_assert(static_cast(FormFieldType::kListBox) == + FPDF_FORMFIELD_LISTBOX, + "ListBox form field types must match"); +static_assert(static_cast(FormFieldType::kTextField) == + FPDF_FORMFIELD_TEXTFIELD, + "TextField form field types must match"); +static_assert(static_cast(FormFieldType::kSignature) == + FPDF_FORMFIELD_SIGNATURE, + "Signature form field types must match"); +#ifdef PDF_ENABLE_XFA +static_assert(static_cast(FormFieldType::kXFA) == FPDF_FORMFIELD_XFA, + "XFA form field types must match"); +static_assert(static_cast(FormFieldType::kXFA_CheckBox) == + FPDF_FORMFIELD_XFA_CHECKBOX, + "XFA CheckBox form field types must match"); +static_assert(static_cast(FormFieldType::kXFA_ComboBox) == + FPDF_FORMFIELD_XFA_COMBOBOX, + "XFA ComboBox form field types must match"); +static_assert(static_cast(FormFieldType::kXFA_ImageField) == + FPDF_FORMFIELD_XFA_IMAGEFIELD, + "XFA ImageField form field types must match"); +static_assert(static_cast(FormFieldType::kXFA_ListBox) == + FPDF_FORMFIELD_XFA_LISTBOX, + "XFA ListBox form field types must match"); +static_assert(static_cast(FormFieldType::kXFA_PushButton) == + FPDF_FORMFIELD_XFA_PUSHBUTTON, + "XFA PushButton form field types must match"); +static_assert(static_cast(FormFieldType::kXFA_Signature) == + FPDF_FORMFIELD_XFA_SIGNATURE, + "XFA Signature form field types must match"); +static_assert(static_cast(FormFieldType::kXFA_TextField) == + FPDF_FORMFIELD_XFA_TEXTFIELD, + "XFA TextField form field types must match"); +#endif // PDF_ENABLE_XFA +static_assert(kFormFieldTypeCount == FPDF_FORMFIELD_COUNT, + "Number of form field types must match"); + +namespace { + +CPDFSDK_FormFillEnvironment* HandleToCPDFSDKEnvironment( + FPDF_FORMHANDLE handle) { + return static_cast(handle); +} + +CPDFSDK_InterForm* FormHandleToInterForm(FPDF_FORMHANDLE hHandle) { + CPDFSDK_FormFillEnvironment* pFormFillEnv = + HandleToCPDFSDKEnvironment(hHandle); + return pFormFillEnv ? pFormFillEnv->GetInterForm() : nullptr; +} + +CPDFSDK_PageView* FormHandleToPageView(FPDF_FORMHANDLE hHandle, + FPDF_PAGE page) { + UnderlyingPageType* pPage = UnderlyingFromFPDFPage(page); + if (!pPage) + return nullptr; + + CPDFSDK_FormFillEnvironment* pFormFillEnv = + HandleToCPDFSDKEnvironment(hHandle); + return pFormFillEnv ? pFormFillEnv->GetPageView(pPage, true) : nullptr; +} + +#ifdef PDF_ENABLE_XFA +std::vector* FromFPDFStringHandle(FPDF_STRINGHANDLE handle) { + return static_cast*>(handle); +} + +FPDF_STRINGHANDLE ToFPDFStringHandle(std::vector* strings) { + return static_cast(strings); +} +#endif // PDF_ENABLE_XFA + +void FFLCommon(FPDF_FORMHANDLE hHandle, + FPDF_BITMAP bitmap, + FPDF_RECORDER recorder, + FPDF_PAGE page, + int start_x, + int start_y, + int size_x, + int size_y, + int rotate, + int flags) { + if (!hHandle) + return; + + UnderlyingPageType* pPage = UnderlyingFromFPDFPage(page); + if (!pPage) + return; + +#ifdef PDF_ENABLE_XFA + CPDFXFA_Context* pContext = pPage->GetContext(); + if (!pContext) + return; + CPDF_Document* pPDFDoc = pContext->GetPDFDoc(); + if (!pPDFDoc) + return; + CPDFSDK_FormFillEnvironment* pFormFillEnv = + HandleToCPDFSDKEnvironment(hHandle); + if (!pFormFillEnv) + return; +#endif // PDF_ENABLE_XFA + + CFX_Matrix matrix = + pPage->GetDisplayMatrix(start_x, start_y, size_x, size_y, rotate); + FX_RECT clip(start_x, start_y, start_x + size_x, start_y + size_y); + + auto pDevice = pdfium::MakeUnique(); +#ifdef _SKIA_SUPPORT_ + pDevice->AttachRecorder(static_cast(recorder)); +#endif + RetainPtr holder(CFXBitmapFromFPDFBitmap(bitmap)); + pDevice->Attach(holder, false, nullptr, false); + { + CFX_RenderDevice::StateRestorer restorer(pDevice.get()); + pDevice->SetClip_Rect(clip); + + CPDF_RenderOptions options; + uint32_t option_flags = options.GetFlags(); + if (flags & FPDF_LCD_TEXT) + option_flags |= RENDER_CLEARTYPE; + else + option_flags &= ~RENDER_CLEARTYPE; + options.SetFlags(option_flags); + + // Grayscale output + if (flags & FPDF_GRAYSCALE) + options.SetColorMode(CPDF_RenderOptions::kGray); + + options.SetDrawAnnots(flags & FPDF_ANNOT); + +#ifdef PDF_ENABLE_XFA + options.SetOCContext( + pdfium::MakeRetain(pPDFDoc, CPDF_OCContext::View)); + if (CPDFSDK_PageView* pPageView = pFormFillEnv->GetPageView(pPage, true)) + pPageView->PageView_OnDraw(pDevice.get(), &matrix, &options, clip); +#else // PDF_ENABLE_XFA + options.SetOCContext(pdfium::MakeRetain( + pPage->m_pDocument.Get(), CPDF_OCContext::View)); + if (CPDFSDK_PageView* pPageView = FormHandleToPageView(hHandle, pPage)) + pPageView->PageView_OnDraw(pDevice.get(), &matrix, &options); +#endif // PDF_ENABLE_XFA + } +#ifdef _SKIA_SUPPORT_PATHS_ + pDevice->Flush(true); + holder->UnPreMultiply(); +#endif +} + +} // namespace + +FPDF_EXPORT int FPDF_CALLCONV +FPDFPage_HasFormFieldAtPoint(FPDF_FORMHANDLE hHandle, + FPDF_PAGE page, + double page_x, + double page_y) { + if (!hHandle) + return -1; + CPDF_Page* pPage = CPDFPageFromFPDFPage(page); + if (pPage) { + CPDF_InterForm interform(pPage->m_pDocument.Get()); + CPDF_FormControl* pFormCtrl = interform.GetControlAtPoint( + pPage, + CFX_PointF(static_cast(page_x), static_cast(page_y)), + nullptr); + if (!pFormCtrl) + return -1; + CPDF_FormField* pFormField = pFormCtrl->GetField(); + return pFormField ? static_cast(pFormField->GetFieldType()) : -1; + } + +#ifdef PDF_ENABLE_XFA + CPDFXFA_Page* pXFAPage = UnderlyingFromFPDFPage(page); + if (!pXFAPage) + return -1; + + CXFA_FFPageView* pPageView = pXFAPage->GetXFAPageView(); + if (!pPageView) + return -1; + + CXFA_FFDocView* pDocView = pPageView->GetDocView(); + if (!pDocView) + return -1; + + CXFA_FFWidgetHandler* pWidgetHandler = pDocView->GetWidgetHandler(); + if (!pWidgetHandler) + return -1; + + std::unique_ptr pWidgetIterator( + pPageView->CreateWidgetIterator(XFA_TRAVERSEWAY_Form, + XFA_WidgetStatus_Viewable)); + if (!pWidgetIterator) + return -1; + + CXFA_FFWidget* pXFAAnnot; + while ((pXFAAnnot = pWidgetIterator->MoveToNext()) != nullptr) { + if (pXFAAnnot->GetFormFieldType() == FormFieldType::kXFA) + continue; + + CFX_RectF rcBBox = pXFAAnnot->GetWidgetRect(); + CFX_FloatRect rcWidget(rcBBox.left, rcBBox.top, rcBBox.left + rcBBox.width, + rcBBox.top + rcBBox.height); + rcWidget.Inflate(1.0f, 1.0f); + if (rcWidget.Contains(CFX_PointF(static_cast(page_x), + static_cast(page_y)))) { + return static_cast(pXFAAnnot->GetFormFieldType()); + } + } +#endif // PDF_ENABLE_XFA + return -1; +} + +FPDF_EXPORT int FPDF_CALLCONV +FPDFPage_FormFieldZOrderAtPoint(FPDF_FORMHANDLE hHandle, + FPDF_PAGE page, + double page_x, + double page_y) { + if (!hHandle) + return -1; + CPDF_Page* pPage = CPDFPageFromFPDFPage(page); + if (!pPage) + return -1; + CPDF_InterForm interform(pPage->m_pDocument.Get()); + int z_order = -1; + (void)interform.GetControlAtPoint( + pPage, CFX_PointF(static_cast(page_x), static_cast(page_y)), + &z_order); + return z_order; +} + +FPDF_EXPORT FPDF_FORMHANDLE FPDF_CALLCONV +FPDFDOC_InitFormFillEnvironment(FPDF_DOCUMENT document, + FPDF_FORMFILLINFO* formInfo) { +#ifdef PDF_ENABLE_XFA + const int kRequiredVersion = 2; +#else // PDF_ENABLE_XFA + const int kRequiredVersion = 1; +#endif // PDF_ENABLE_XFA + if (!formInfo || formInfo->version != kRequiredVersion) + return nullptr; + + UnderlyingDocumentType* pDocument = UnderlyingFromFPDFDocument(document); + if (!pDocument) + return nullptr; + +#ifdef PDF_ENABLE_XFA + // If the CPDFXFA_Context has a FormFillEnvironment already then we've done + // this and can just return the old Env. Otherwise, we'll end up setting a new + // environment into the XFADocument and, that could get weird. + if (pDocument->GetFormFillEnv()) + return pDocument->GetFormFillEnv(); +#endif + + auto pFormFillEnv = + pdfium::MakeUnique(pDocument, formInfo); + +#ifdef PDF_ENABLE_XFA + pDocument->SetFormFillEnv(pFormFillEnv.get()); +#endif // PDF_ENABLE_XFA + + return pFormFillEnv.release(); // Caller takes ownership. +} + +FPDF_EXPORT void FPDF_CALLCONV +FPDFDOC_ExitFormFillEnvironment(FPDF_FORMHANDLE hHandle) { + CPDFSDK_FormFillEnvironment* pFormFillEnv = + HandleToCPDFSDKEnvironment(hHandle); + if (!pFormFillEnv) + return; + +#ifdef PDF_ENABLE_XFA + // Reset the focused annotations and remove the SDK document from the + // XFA document. + pFormFillEnv->ClearAllFocusedAnnots(); + // If the document was closed first, it's possible the XFA document + // is now a nullptr. + if (pFormFillEnv->GetXFAContext()) + pFormFillEnv->GetXFAContext()->SetFormFillEnv(nullptr); +#endif // PDF_ENABLE_XFA + delete pFormFillEnv; +} + +FPDF_EXPORT FPDF_BOOL FPDF_CALLCONV FORM_OnMouseMove(FPDF_FORMHANDLE hHandle, + FPDF_PAGE page, + int modifier, + double page_x, + double page_y) { + CPDFSDK_PageView* pPageView = FormHandleToPageView(hHandle, page); + if (!pPageView) + return false; + return pPageView->OnMouseMove(CFX_PointF(page_x, page_y), modifier); +} + +FPDF_EXPORT FPDF_BOOL FPDF_CALLCONV FORM_OnFocus(FPDF_FORMHANDLE hHandle, + FPDF_PAGE page, + int modifier, + double page_x, + double page_y) { + CPDFSDK_PageView* pPageView = FormHandleToPageView(hHandle, page); + if (!pPageView) + return false; + return pPageView->OnFocus(CFX_PointF(page_x, page_y), modifier); +} + +FPDF_EXPORT FPDF_BOOL FPDF_CALLCONV FORM_OnLButtonDown(FPDF_FORMHANDLE hHandle, + FPDF_PAGE page, + int modifier, + double page_x, + double page_y) { + CPDFSDK_PageView* pPageView = FormHandleToPageView(hHandle, page); + if (!pPageView) + return false; + return pPageView->OnLButtonDown(CFX_PointF(page_x, page_y), modifier); +} + +FPDF_EXPORT FPDF_BOOL FPDF_CALLCONV FORM_OnLButtonUp(FPDF_FORMHANDLE hHandle, + FPDF_PAGE page, + int modifier, + double page_x, + double page_y) { + CPDFSDK_PageView* pPageView = FormHandleToPageView(hHandle, page); + if (!pPageView) + return false; + return pPageView->OnLButtonUp(CFX_PointF(page_x, page_y), modifier); +} + +#ifdef PDF_ENABLE_XFA +FPDF_EXPORT FPDF_BOOL FPDF_CALLCONV FORM_OnRButtonDown(FPDF_FORMHANDLE hHandle, + FPDF_PAGE page, + int modifier, + double page_x, + double page_y) { + CPDFSDK_PageView* pPageView = FormHandleToPageView(hHandle, page); + if (!pPageView) + return false; + return pPageView->OnRButtonDown(CFX_PointF(page_x, page_y), modifier); +} + +FPDF_EXPORT FPDF_BOOL FPDF_CALLCONV FORM_OnRButtonUp(FPDF_FORMHANDLE hHandle, + FPDF_PAGE page, + int modifier, + double page_x, + double page_y) { + CPDFSDK_PageView* pPageView = FormHandleToPageView(hHandle, page); + if (!pPageView) + return false; + return pPageView->OnRButtonUp(CFX_PointF(page_x, page_y), modifier); +} +#endif // PDF_ENABLE_XFA + +FPDF_EXPORT FPDF_BOOL FPDF_CALLCONV FORM_OnKeyDown(FPDF_FORMHANDLE hHandle, + FPDF_PAGE page, + int nKeyCode, + int modifier) { + CPDFSDK_PageView* pPageView = FormHandleToPageView(hHandle, page); + if (!pPageView) + return false; + return pPageView->OnKeyDown(nKeyCode, modifier); +} + +FPDF_EXPORT FPDF_BOOL FPDF_CALLCONV FORM_OnKeyUp(FPDF_FORMHANDLE hHandle, + FPDF_PAGE page, + int nKeyCode, + int modifier) { + CPDFSDK_PageView* pPageView = FormHandleToPageView(hHandle, page); + if (!pPageView) + return false; + return pPageView->OnKeyUp(nKeyCode, modifier); +} + +FPDF_EXPORT FPDF_BOOL FPDF_CALLCONV FORM_OnChar(FPDF_FORMHANDLE hHandle, + FPDF_PAGE page, + int nChar, + int modifier) { + CPDFSDK_PageView* pPageView = FormHandleToPageView(hHandle, page); + if (!pPageView) + return false; + return pPageView->OnChar(nChar, modifier); +} + +FPDF_EXPORT unsigned long FPDF_CALLCONV +FORM_GetSelectedText(FPDF_FORMHANDLE hHandle, + FPDF_PAGE page, + void* buffer, + unsigned long buflen) { + CPDFSDK_PageView* pPageView = FormHandleToPageView(hHandle, page); + if (!pPageView) + return 0; + + WideString wide_str_form_text = pPageView->GetSelectedText(); + ByteString encoded_form_text = wide_str_form_text.UTF16LE_Encode(); + unsigned long form_text_len = encoded_form_text.GetLength(); + + if (buffer && buflen >= form_text_len) + memcpy(buffer, encoded_form_text.c_str(), form_text_len); + + return form_text_len; +} + +FPDF_EXPORT void FPDF_CALLCONV FORM_ReplaceSelection(FPDF_FORMHANDLE hHandle, + FPDF_PAGE page, + FPDF_WIDESTRING wsText) { + CPDFSDK_PageView* pPageView = FormHandleToPageView(hHandle, page); + if (!pPageView) + return; + + size_t len = WideString::WStringLength(wsText); + WideString wide_str_text = WideString::FromUTF16LE(wsText, len); + + pPageView->ReplaceSelection(wide_str_text); +} + +FPDF_EXPORT FPDF_BOOL FPDF_CALLCONV +FORM_ForceToKillFocus(FPDF_FORMHANDLE hHandle) { + CPDFSDK_FormFillEnvironment* pFormFillEnv = + HandleToCPDFSDKEnvironment(hHandle); + if (!pFormFillEnv) + return false; + return pFormFillEnv->KillFocusAnnot(0); +} + +FPDF_EXPORT void FPDF_CALLCONV FPDF_FFLDraw(FPDF_FORMHANDLE hHandle, + FPDF_BITMAP bitmap, + FPDF_PAGE page, + int start_x, + int start_y, + int size_x, + int size_y, + int rotate, + int flags) { + FFLCommon(hHandle, bitmap, nullptr, page, start_x, start_y, size_x, size_y, + rotate, flags); +} + +#ifdef _SKIA_SUPPORT_ +FPDF_EXPORT void FPDF_CALLCONV FPDF_FFLRecord(FPDF_FORMHANDLE hHandle, + FPDF_RECORDER recorder, + FPDF_PAGE page, + int start_x, + int start_y, + int size_x, + int size_y, + int rotate, + int flags) { + FFLCommon(hHandle, nullptr, recorder, page, start_x, start_y, size_x, size_y, + rotate, flags); +} +#endif + +#ifdef PDF_ENABLE_XFA +FPDF_EXPORT void FPDF_CALLCONV FPDF_Widget_Undo(FPDF_DOCUMENT document, + FPDF_WIDGET hWidget) { + if (!hWidget || !document) + return; + + CPDFXFA_Context* pContext = static_cast(document); + if (!pContext->ContainsXFAForm()) + return; + + static_cast(hWidget)->Undo(); +} + +FPDF_EXPORT void FPDF_CALLCONV FPDF_Widget_Redo(FPDF_DOCUMENT document, + FPDF_WIDGET hWidget) { + if (!hWidget || !document) + return; + + CPDFXFA_Context* pContext = static_cast(document); + if (!pContext->ContainsXFAForm()) + return; + + static_cast(hWidget)->Redo(); +} + +FPDF_EXPORT void FPDF_CALLCONV FPDF_Widget_SelectAll(FPDF_DOCUMENT document, + FPDF_WIDGET hWidget) { + if (!hWidget || !document) + return; + + CPDFXFA_Context* pContext = static_cast(document); + if (!pContext->ContainsXFAForm()) + return; + + static_cast(hWidget)->SelectAll(); +} + +FPDF_EXPORT void FPDF_CALLCONV FPDF_Widget_Copy(FPDF_DOCUMENT document, + FPDF_WIDGET hWidget, + FPDF_WIDESTRING wsText, + FPDF_DWORD* size) { + if (!hWidget || !document) + return; + + CPDFXFA_Context* pContext = static_cast(document); + if (!pContext->ContainsXFAForm()) + return; + + WideString wsCpText = + static_cast(hWidget)->Copy().value_or(WideString()); + + ByteString bsCpText = wsCpText.UTF16LE_Encode(); + uint32_t len = bsCpText.GetLength() / sizeof(unsigned short); + if (!wsText) { + *size = len; + return; + } + + uint32_t real_size = len < *size ? len : *size; + if (real_size > 0) { + memcpy((void*)wsText, + bsCpText.GetBuffer(real_size * sizeof(unsigned short)), + real_size * sizeof(unsigned short)); + bsCpText.ReleaseBuffer(real_size * sizeof(unsigned short)); + } + *size = real_size; +} + +FPDF_EXPORT void FPDF_CALLCONV FPDF_Widget_Cut(FPDF_DOCUMENT document, + FPDF_WIDGET hWidget, + FPDF_WIDESTRING wsText, + FPDF_DWORD* size) { + if (!hWidget || !document) + return; + + CPDFXFA_Context* pContext = static_cast(document); + if (!pContext->ContainsXFAForm()) + return; + + WideString wsCpText = + static_cast(hWidget)->Cut().value_or(WideString()); + + ByteString bsCpText = wsCpText.UTF16LE_Encode(); + uint32_t len = bsCpText.GetLength() / sizeof(unsigned short); + if (!wsText) { + *size = len; + return; + } + + uint32_t real_size = len < *size ? len : *size; + if (real_size > 0) { + memcpy((void*)wsText, + bsCpText.GetBuffer(real_size * sizeof(unsigned short)), + real_size * sizeof(unsigned short)); + bsCpText.ReleaseBuffer(real_size * sizeof(unsigned short)); + } + *size = real_size; +} + +FPDF_EXPORT void FPDF_CALLCONV FPDF_Widget_Paste(FPDF_DOCUMENT document, + FPDF_WIDGET hWidget, + FPDF_WIDESTRING wsText, + FPDF_DWORD size) { + if (!hWidget || !document) + return; + + CPDFXFA_Context* pContext = static_cast(document); + if (!pContext->ContainsXFAForm()) + return; + + WideString wstr = WideString::FromUTF16LE(wsText, size); + static_cast(hWidget)->Paste(wstr); +} + +FPDF_EXPORT void FPDF_CALLCONV +FPDF_Widget_ReplaceSpellCheckWord(FPDF_DOCUMENT document, + FPDF_WIDGET hWidget, + float x, + float y, + FPDF_BYTESTRING bsText) { + if (!hWidget || !document) + return; + + CPDFXFA_Context* pContext = static_cast(document); + if (!pContext->ContainsXFAForm()) + return; + + CFX_PointF ptPopup; + ptPopup.x = x; + ptPopup.y = y; + ByteStringView bs(bsText); + static_cast(hWidget)->ReplaceSpellCheckWord(ptPopup, bs); +} + +FPDF_EXPORT void FPDF_CALLCONV +FPDF_Widget_GetSpellCheckWords(FPDF_DOCUMENT document, + FPDF_WIDGET hWidget, + float x, + float y, + FPDF_STRINGHANDLE* stringHandle) { + if (!hWidget || !document) + return; + + auto* pContext = static_cast(document); + if (!pContext->ContainsXFAForm()) + return; + + CFX_PointF ptPopup; + ptPopup.x = x; + ptPopup.y = y; + auto sSuggestWords = pdfium::MakeUnique>(); + static_cast(hWidget)->GetSuggestWords(ptPopup, + sSuggestWords.get()); + + // Caller takes ownership. + *stringHandle = ToFPDFStringHandle(sSuggestWords.release()); +} + +FPDF_EXPORT int FPDF_CALLCONV +FPDF_StringHandleCounts(FPDF_STRINGHANDLE sHandle) { + std::vector* sSuggestWords = FromFPDFStringHandle(sHandle); + return sSuggestWords ? pdfium::CollectionSize(*sSuggestWords) : -1; +} + +FPDF_EXPORT FPDF_BOOL FPDF_CALLCONV +FPDF_StringHandleGetStringByIndex(FPDF_STRINGHANDLE sHandle, + int index, + FPDF_BYTESTRING bsText, + FPDF_DWORD* size) { + if (!sHandle || !size) + return false; + + int count = FPDF_StringHandleCounts(sHandle); + if (index < 0 || index >= count) + return false; + + std::vector* sSuggestWords = FromFPDFStringHandle(sHandle); + uint32_t len = (*sSuggestWords)[index].GetLength(); + if (!bsText) { + *size = len; + return true; + } + + uint32_t real_size = len < *size ? len : *size; + if (real_size > 0) + memcpy((void*)bsText, (*sSuggestWords)[index].c_str(), real_size); + *size = real_size; + return true; +} + +FPDF_EXPORT void FPDF_CALLCONV +FPDF_StringHandleRelease(FPDF_STRINGHANDLE stringHandle) { + delete FromFPDFStringHandle(stringHandle); +} + +FPDF_EXPORT FPDF_BOOL FPDF_CALLCONV +FPDF_StringHandleAddString(FPDF_STRINGHANDLE stringHandle, + FPDF_BYTESTRING bsText, + FPDF_DWORD size) { + if (!stringHandle || !bsText || size == 0) + return false; + + FromFPDFStringHandle(stringHandle)->push_back(ByteString(bsText, size)); + return true; +} +#endif // PDF_ENABLE_XFA + +FPDF_EXPORT void FPDF_CALLCONV +FPDF_SetFormFieldHighlightColor(FPDF_FORMHANDLE hHandle, + int fieldType, + unsigned long color) { + CPDFSDK_InterForm* interForm = FormHandleToInterForm(hHandle); + if (!interForm) + return; + + Optional cast_input = IntToFormFieldType(fieldType); + if (!cast_input) + return; + + if (cast_input.value() == FormFieldType::kUnknown) { + interForm->SetAllHighlightColors(color); + } else { + interForm->SetHighlightColor(color, cast_input.value()); + } +} + +FPDF_EXPORT void FPDF_CALLCONV +FPDF_SetFormFieldHighlightAlpha(FPDF_FORMHANDLE hHandle, unsigned char alpha) { + if (CPDFSDK_InterForm* pInterForm = FormHandleToInterForm(hHandle)) + pInterForm->SetHighlightAlpha(alpha); +} + +FPDF_EXPORT void FPDF_CALLCONV +FPDF_RemoveFormFieldHighlight(FPDF_FORMHANDLE hHandle) { + if (CPDFSDK_InterForm* pInterForm = FormHandleToInterForm(hHandle)) + pInterForm->RemoveAllHighLights(); +} + +FPDF_EXPORT void FPDF_CALLCONV FORM_OnAfterLoadPage(FPDF_PAGE page, + FPDF_FORMHANDLE hHandle) { + if (CPDFSDK_PageView* pPageView = FormHandleToPageView(hHandle, page)) + pPageView->SetValid(true); +} + +FPDF_EXPORT void FPDF_CALLCONV FORM_OnBeforeClosePage(FPDF_PAGE page, + FPDF_FORMHANDLE hHandle) { + CPDFSDK_FormFillEnvironment* pFormFillEnv = + HandleToCPDFSDKEnvironment(hHandle); + if (!pFormFillEnv) + return; + + UnderlyingPageType* pPage = UnderlyingFromFPDFPage(page); + if (!pPage) + return; + + CPDFSDK_PageView* pPageView = pFormFillEnv->GetPageView(pPage, false); + if (pPageView) { + pPageView->SetValid(false); + // RemovePageView() takes care of the delete for us. + pFormFillEnv->RemovePageView(pPage); + } +} + +FPDF_EXPORT void FPDF_CALLCONV +FORM_DoDocumentJSAction(FPDF_FORMHANDLE hHandle) { + CPDFSDK_FormFillEnvironment* pFormFillEnv = + HandleToCPDFSDKEnvironment(hHandle); + if (pFormFillEnv && pFormFillEnv->IsJSPlatformPresent()) + pFormFillEnv->ProcJavascriptFun(); +} + +FPDF_EXPORT void FPDF_CALLCONV +FORM_DoDocumentOpenAction(FPDF_FORMHANDLE hHandle) { + CPDFSDK_FormFillEnvironment* pFormFillEnv = + HandleToCPDFSDKEnvironment(hHandle); + if (pFormFillEnv && pFormFillEnv->IsJSPlatformPresent()) + pFormFillEnv->ProcOpenAction(); +} + +FPDF_EXPORT void FPDF_CALLCONV FORM_DoDocumentAAction(FPDF_FORMHANDLE hHandle, + int aaType) { + CPDFSDK_FormFillEnvironment* pFormFillEnv = + HandleToCPDFSDKEnvironment(hHandle); + if (!pFormFillEnv) + return; + + CPDF_Document* pDoc = pFormFillEnv->GetPDFDocument(); + const CPDF_Dictionary* pDict = pDoc->GetRoot(); + if (!pDict) + return; + + CPDF_AAction aa(pDict->GetDictFor("AA")); + auto type = static_cast(aaType); + if (aa.ActionExist(type)) { + CPDF_Action action = aa.GetAction(type); + CPDFSDK_ActionHandler* pActionHandler = + HandleToCPDFSDKEnvironment(hHandle)->GetActionHandler(); + pActionHandler->DoAction_Document(action, type, pFormFillEnv); + } +} + +FPDF_EXPORT void FPDF_CALLCONV FORM_DoPageAAction(FPDF_PAGE page, + FPDF_FORMHANDLE hHandle, + int aaType) { + CPDFSDK_FormFillEnvironment* pFormFillEnv = + HandleToCPDFSDKEnvironment(hHandle); + if (!pFormFillEnv) + return; + + UnderlyingPageType* pPage = UnderlyingFromFPDFPage(page); + CPDF_Page* pPDFPage = CPDFPageFromFPDFPage(page); + if (!pPDFPage) + return; + + if (!pFormFillEnv->GetPageView(pPage, false)) + return; + + CPDFSDK_ActionHandler* pActionHandler = pFormFillEnv->GetActionHandler(); + CPDF_Dictionary* pPageDict = pPDFPage->m_pFormDict.Get(); + CPDF_AAction aa(pPageDict->GetDictFor("AA")); + CPDF_AAction::AActionType type = aaType == FPDFPAGE_AACTION_OPEN + ? CPDF_AAction::OpenPage + : CPDF_AAction::ClosePage; + if (aa.ActionExist(type)) { + CPDF_Action action = aa.GetAction(type); + pActionHandler->DoAction_Page(action, type, pFormFillEnv); + } +} diff --git a/fpdfsdk/fpdf_formfill_embeddertest.cpp b/fpdfsdk/fpdf_formfill_embeddertest.cpp new file mode 100644 index 0000000000..99a51fd4a4 --- /dev/null +++ b/fpdfsdk/fpdf_formfill_embeddertest.cpp @@ -0,0 +1,1376 @@ +// Copyright 2015 PDFium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include +#include +#include + +#include "core/fxcrt/fx_coordinates.h" +#include "core/fxcrt/fx_string.h" +#include "core/fxcrt/fx_system.h" +#include "public/cpp/fpdf_deleters.h" +#include "public/fpdf_formfill.h" +#include "public/fpdf_fwlevent.h" +#include "testing/embedder_test.h" +#include "testing/embedder_test_mock_delegate.h" +#include "testing/embedder_test_timer_handling_delegate.h" +#include "testing/gmock/include/gmock/gmock.h" +#include "testing/gtest/include/gtest/gtest.h" + +using testing::_; + +using FPDFFormFillEmbeddertest = EmbedderTest; + +// A base class for many related tests that involve clicking and typing into +// form fields. +class FPDFFormFillInteractiveEmbeddertest : public FPDFFormFillEmbeddertest { + protected: + FPDFFormFillInteractiveEmbeddertest() = default; + ~FPDFFormFillInteractiveEmbeddertest() override = default; + + void SetUp() override { + FPDFFormFillEmbeddertest::SetUp(); + ASSERT_TRUE(OpenDocument(GetDocumentName())); + page_ = LoadPage(0); + ASSERT_TRUE(page_); + FormSanityChecks(); + } + + void TearDown() override { + UnloadPage(page_); + FPDFFormFillEmbeddertest::TearDown(); + } + + // Returns the name of the PDF to use. + virtual const char* GetDocumentName() const = 0; + + // Returns the type of field(s) in the PDF. + virtual int GetFormType() const = 0; + + // Optionally do some sanity check on the document after loading. + virtual void FormSanityChecks() {} + + FPDF_PAGE page() { return page_; } + + int GetFormTypeAtPoint(const CFX_PointF& point) { + return FPDFPage_HasFormFieldAtPoint(form_handle(), page_, point.x, point.y); + } + + void ClickOnFormFieldAtPoint(const CFX_PointF& point) { + // Click on the text field or combobox as specified by coordinates. + FORM_OnMouseMove(form_handle(), page_, 0, point.x, point.y); + FORM_OnLButtonDown(form_handle(), page_, 0, point.x, point.y); + FORM_OnLButtonUp(form_handle(), page_, 0, point.x, point.y); + } + + void TypeTextIntoTextField(int num_chars, const CFX_PointF& point) { + EXPECT_EQ(GetFormType(), GetFormTypeAtPoint(point)); + ClickOnFormFieldAtPoint(point); + + // Type text starting with 'A' to as many chars as specified by |num_chars|. + for (int i = 0; i < num_chars; ++i) { + FORM_OnChar(form_handle(), page_, 'A' + i, 0); + } + } + + // Navigates to text field using the mouse and then selects text via the + // shift and specfied left or right arrow key. + void SelectTextWithKeyboard(int num_chars, + int arrow_key, + const CFX_PointF& point) { + // Navigate to starting position for selection. + ClickOnFormFieldAtPoint(point); + + // Hold down shift (and don't release until entire text is selected). + FORM_OnKeyDown(form_handle(), page_, FWL_VKEY_Shift, 0); + + // Select text char by char via left or right arrow key. + for (int i = 0; i < num_chars; ++i) { + FORM_OnKeyDown(form_handle(), page_, arrow_key, FWL_EVENTFLAG_ShiftKey); + FORM_OnKeyUp(form_handle(), page_, arrow_key, FWL_EVENTFLAG_ShiftKey); + } + FORM_OnKeyUp(form_handle(), page_, FWL_VKEY_Shift, 0); + } + + // Uses the mouse to navigate to text field and select text. + void SelectTextWithMouse(const CFX_PointF& start, const CFX_PointF& end) { + ASSERT(start.y == end.y); + + // Navigate to starting position and click mouse. + FORM_OnMouseMove(form_handle(), page_, 0, start.x, start.y); + FORM_OnLButtonDown(form_handle(), page_, 0, start.x, start.y); + + // Hold down mouse until reach end of desired selection. + FORM_OnMouseMove(form_handle(), page_, 0, end.x, end.y); + FORM_OnLButtonUp(form_handle(), page_, 0, end.x, end.y); + } + + void CheckSelection(const WideStringView& expected_string) { + // Calculate expected length for selected text. + int num_chars = expected_string.GetLength(); + + // Check actual selection against expected selection. + const unsigned long expected_length = + sizeof(unsigned short) * (num_chars + 1); + unsigned long sel_text_len = + FORM_GetSelectedText(form_handle(), page_, nullptr, 0); + ASSERT_EQ(expected_length, sel_text_len); + + std::vector buf(sel_text_len); + EXPECT_EQ(expected_length, FORM_GetSelectedText(form_handle(), page_, + buf.data(), sel_text_len)); + + EXPECT_EQ(expected_string, WideString::FromUTF16LE(buf.data(), num_chars)); + } + + private: + FPDF_PAGE page_ = nullptr; +}; + +class FPDFFormFillTextFormEmbeddertest + : public FPDFFormFillInteractiveEmbeddertest { + protected: + FPDFFormFillTextFormEmbeddertest() = default; + ~FPDFFormFillTextFormEmbeddertest() override = default; + + const char* GetDocumentName() const override { + // PDF with several form text fields: + // - "Text Box" - Regular text box with no special attributes. + // - "ReadOnly" - Ff: 1. + // - "CharLimit" - MaxLen: 10, V: Elephant. + return "text_form_multiple.pdf"; + } + + int GetFormType() const override { return FPDF_FORMFIELD_TEXTFIELD; } + + void FormSanityChecks() override { + EXPECT_EQ(GetFormType(), GetFormTypeAtPoint(CharLimitFormBegin())); + EXPECT_EQ(GetFormType(), GetFormTypeAtPoint(CharLimitFormEnd())); + EXPECT_EQ(GetFormType(), GetFormTypeAtPoint(RegularFormBegin())); + EXPECT_EQ(GetFormType(), GetFormTypeAtPoint(RegularFormEnd())); + } + + void SelectAllCharLimitFormTextWithMouse() { + SelectTextWithMouse(CharLimitFormEnd(), CharLimitFormBegin()); + } + + void SelectAllRegularFormTextWithMouse() { + SelectTextWithMouse(RegularFormEnd(), RegularFormBegin()); + } + + const CFX_PointF& CharLimitFormBegin() const { + static const CFX_PointF point = CharLimitFormAtX(kFormBeginX); + return point; + } + + const CFX_PointF& CharLimitFormEnd() const { + static const CFX_PointF point = CharLimitFormAtX(kFormEndX); + return point; + } + + const CFX_PointF& RegularFormBegin() const { + static const CFX_PointF point = RegularFormAtX(kFormBeginX); + return point; + } + + const CFX_PointF& RegularFormEnd() const { + static const CFX_PointF point = RegularFormAtX(kFormEndX); + return point; + } + + static CFX_PointF CharLimitFormAtX(float x) { + ASSERT(x >= kFormBeginX); + ASSERT(x <= kFormEndX); + return CFX_PointF(x, kCharLimitFormY); + } + + static CFX_PointF RegularFormAtX(float x) { + ASSERT(x >= kFormBeginX); + ASSERT(x <= kFormEndX); + return CFX_PointF(x, kRegularFormY); + } + + private: + static constexpr float kFormBeginX = 102.0; + static constexpr float kFormEndX = 195.0; + static constexpr float kCharLimitFormY = 60.0; + static constexpr float kRegularFormY = 115.0; +}; + +class FPDFFormFillComboBoxFormEmbeddertest + : public FPDFFormFillInteractiveEmbeddertest { + protected: + FPDFFormFillComboBoxFormEmbeddertest() = default; + ~FPDFFormFillComboBoxFormEmbeddertest() override = default; + + const char* GetDocumentName() const override { + // PDF with form comboboxes: + // - "Combo_Editable" - Ff: 393216, 3 options with pair values. + // - "Combo1" - Ff: 131072, 3 options with single values. + // - "Combo_ReadOnly" - Ff: 131073, 3 options with single values. + return "combobox_form.pdf"; + } + + int GetFormType() const override { return FPDF_FORMFIELD_COMBOBOX; } + + void FormSanityChecks() override { + EXPECT_EQ(GetFormType(), GetFormTypeAtPoint(EditableFormBegin())); + EXPECT_EQ(GetFormType(), GetFormTypeAtPoint(EditableFormEnd())); + EXPECT_EQ(GetFormType(), GetFormTypeAtPoint(EditableFormDropDown())); + EXPECT_EQ(GetFormType(), GetFormTypeAtPoint(NonEditableFormBegin())); + EXPECT_EQ(GetFormType(), GetFormTypeAtPoint(NonEditableFormEnd())); + EXPECT_EQ(GetFormType(), GetFormTypeAtPoint(NonEditableFormDropDown())); + } + + void SelectEditableFormOption(int item_index) { + ASSERT(item_index >= 0); + ASSERT(item_index < 3); + SelectOption(item_index, EditableFormDropDown()); + } + + void SelectNonEditableFormOption(int item_index) { + ASSERT(item_index >= 0); + ASSERT(item_index < 26); + SelectOption(item_index, NonEditableFormDropDown()); + } + + void SelectAllEditableFormTextWithMouse() { + SelectTextWithMouse(EditableFormEnd(), EditableFormBegin()); + } + + const CFX_PointF& EditableFormBegin() const { + static const CFX_PointF point = EditableFormAtX(kFormBeginX); + return point; + } + + const CFX_PointF& EditableFormEnd() const { + static const CFX_PointF point = EditableFormAtX(kFormEndX); + return point; + } + + const CFX_PointF& EditableFormDropDown() const { + static const CFX_PointF point(kFormDropDownX, kEditableFormY); + return point; + } + + const CFX_PointF& NonEditableFormBegin() const { + static const CFX_PointF point = NonEditableFormAtX(kFormBeginX); + return point; + } + + const CFX_PointF& NonEditableFormEnd() const { + static const CFX_PointF point = NonEditableFormAtX(kFormEndX); + return point; + } + + const CFX_PointF& NonEditableFormDropDown() const { + static const CFX_PointF point(kFormDropDownX, kNonEditableFormY); + return point; + } + + static CFX_PointF EditableFormAtX(float x) { + ASSERT(x >= kFormBeginX); + ASSERT(x <= kFormEndX); + return CFX_PointF(x, kEditableFormY); + } + + static CFX_PointF NonEditableFormAtX(float x) { + ASSERT(x >= kFormBeginX); + ASSERT(x <= kFormEndX); + return CFX_PointF(x, kNonEditableFormY); + } + + private: + // Selects one of the pre-selected values from a combobox with three options. + // Options are specified by |item_index|, which is 0-based. + void SelectOption(int item_index, const CFX_PointF& point) { + // Navigate to button for drop down and click mouse to reveal options. + ClickOnFormFieldAtPoint(point); + + // Calculate to Y-coordinate of dropdown option to be selected. + constexpr double kChoiceHeight = 15; + CFX_PointF option_point = point; + option_point.y -= kChoiceHeight * (item_index + 1); + + // Move left to avoid scrollbar. + option_point.x -= 20; + + // Navigate to option and click mouse to select it. + ClickOnFormFieldAtPoint(option_point); + } + + static constexpr float kFormBeginX = 102.0; + static constexpr float kFormEndX = 183.0; + static constexpr float kFormDropDownX = 192.0; + static constexpr float kEditableFormY = 360.0; + static constexpr float kNonEditableFormY = 410.0; +}; + +TEST_F(FPDFFormFillEmbeddertest, FirstTest) { + EmbedderTestMockDelegate mock; + EXPECT_CALL(mock, Alert(_, _, _, _)).Times(0); + EXPECT_CALL(mock, UnsupportedHandler(_)).Times(0); + EXPECT_CALL(mock, SetTimer(_, _)).Times(0); + EXPECT_CALL(mock, KillTimer(_)).Times(0); + SetDelegate(&mock); + + EXPECT_TRUE(OpenDocument("hello_world.pdf")); + FPDF_PAGE page = LoadPage(0); + EXPECT_TRUE(page); + UnloadPage(page); +} + +TEST_F(FPDFFormFillEmbeddertest, BUG_487928) { + EmbedderTestTimerHandlingDelegate delegate; + SetDelegate(&delegate); + + EXPECT_TRUE(OpenDocument("bug_487928.pdf")); + FPDF_PAGE page = LoadPage(0); + EXPECT_TRUE(page); + DoOpenActions(); + delegate.AdvanceTime(5000); + UnloadPage(page); +} + +TEST_F(FPDFFormFillEmbeddertest, BUG_507316) { + EmbedderTestTimerHandlingDelegate delegate; + SetDelegate(&delegate); + + EXPECT_TRUE(OpenDocument("bug_507316.pdf")); + FPDF_PAGE page = LoadPage(2); + EXPECT_TRUE(page); + DoOpenActions(); + delegate.AdvanceTime(4000); + UnloadPage(page); +} + +TEST_F(FPDFFormFillEmbeddertest, BUG_514690) { + EXPECT_TRUE(OpenDocument("hello_world.pdf")); + FPDF_PAGE page = LoadPage(0); + EXPECT_TRUE(page); + + // Test that FORM_OnMouseMove() etc. permit null HANDLES and PAGES. + FORM_OnMouseMove(nullptr, page, 0, 10.0, 10.0); + FORM_OnMouseMove(form_handle(), nullptr, 0, 10.0, 10.0); + + UnloadPage(page); +} + +#ifdef PDF_ENABLE_V8 +TEST_F(FPDFFormFillEmbeddertest, BUG_551248) { + // Test that timers fire once and intervals fire repeatedly. + EmbedderTestTimerHandlingDelegate delegate; + SetDelegate(&delegate); + + EXPECT_TRUE(OpenDocument("bug_551248.pdf")); + FPDF_PAGE page = LoadPage(0); + EXPECT_TRUE(page); + DoOpenActions(); + + const auto& alerts = delegate.GetAlerts(); + EXPECT_EQ(0U, alerts.size()); + + delegate.AdvanceTime(1000); + EXPECT_EQ(0U, alerts.size()); // nothing fired. + delegate.AdvanceTime(1000); + EXPECT_EQ(1U, alerts.size()); // interval fired. + delegate.AdvanceTime(1000); + EXPECT_EQ(2U, alerts.size()); // timer fired. + delegate.AdvanceTime(1000); + EXPECT_EQ(3U, alerts.size()); // interval fired again. + delegate.AdvanceTime(1000); + EXPECT_EQ(3U, alerts.size()); // nothing fired. + delegate.AdvanceTime(1000); + EXPECT_EQ(4U, alerts.size()); // interval fired again. + delegate.AdvanceTime(1000); + EXPECT_EQ(4U, alerts.size()); // nothing fired. + UnloadPage(page); + + ASSERT_EQ(4U, alerts.size()); // nothing else fired. + + EXPECT_STREQ(L"interval fired", alerts[0].message.c_str()); + EXPECT_STREQ(L"Alert", alerts[0].title.c_str()); + EXPECT_EQ(0, alerts[0].type); + EXPECT_EQ(0, alerts[0].icon); + + EXPECT_STREQ(L"timer fired", alerts[1].message.c_str()); + EXPECT_STREQ(L"Alert", alerts[1].title.c_str()); + EXPECT_EQ(0, alerts[1].type); + EXPECT_EQ(0, alerts[1].icon); + + EXPECT_STREQ(L"interval fired", alerts[2].message.c_str()); + EXPECT_STREQ(L"Alert", alerts[2].title.c_str()); + EXPECT_EQ(0, alerts[2].type); + EXPECT_EQ(0, alerts[2].icon); + + EXPECT_STREQ(L"interval fired", alerts[3].message.c_str()); + EXPECT_STREQ(L"Alert", alerts[3].title.c_str()); + EXPECT_EQ(0, alerts[3].type); + EXPECT_EQ(0, alerts[3].icon); +} + +TEST_F(FPDFFormFillEmbeddertest, BUG_620428) { + // Test that timers and intervals are cancelable. + EmbedderTestTimerHandlingDelegate delegate; + SetDelegate(&delegate); + + EXPECT_TRUE(OpenDocument("bug_620428.pdf")); + FPDF_PAGE page = LoadPage(0); + EXPECT_TRUE(page); + DoOpenActions(); + delegate.AdvanceTime(5000); + UnloadPage(page); + + const auto& alerts = delegate.GetAlerts(); + ASSERT_EQ(1U, alerts.size()); + EXPECT_STREQ(L"done", alerts[0].message.c_str()); +} + +TEST_F(FPDFFormFillEmbeddertest, BUG_634394) { + // Cancel timer inside timer callback. + EmbedderTestTimerHandlingDelegate delegate; + SetDelegate(&delegate); + + EXPECT_TRUE(OpenDocument("bug_634394.pdf")); + FPDF_PAGE page = LoadPage(0); + EXPECT_TRUE(page); + DoOpenActions(); + + // Timers fire at most once per AdvanceTime(), allow intervals + // to fire several times if possible. + delegate.AdvanceTime(1000); + delegate.AdvanceTime(1000); + delegate.AdvanceTime(1000); + delegate.AdvanceTime(1000); + delegate.AdvanceTime(1000); + UnloadPage(page); + + const auto& alerts = delegate.GetAlerts(); + EXPECT_EQ(2U, alerts.size()); +} + +TEST_F(FPDFFormFillEmbeddertest, BUG_634716) { + EmbedderTestTimerHandlingDelegate delegate; + SetDelegate(&delegate); + + EXPECT_TRUE(OpenDocument("bug_634716.pdf")); + FPDF_PAGE page = LoadPage(0); + EXPECT_TRUE(page); + DoOpenActions(); + + // Timers fire at most once per AdvanceTime(), allow intervals + // to fire several times if possible. + delegate.AdvanceTime(1000); + delegate.AdvanceTime(1000); + delegate.AdvanceTime(1000); + delegate.AdvanceTime(1000); + delegate.AdvanceTime(1000); + UnloadPage(page); + + const auto& alerts = delegate.GetAlerts(); + EXPECT_EQ(2U, alerts.size()); +} + +TEST_F(FPDFFormFillEmbeddertest, BUG_679649) { + EmbedderTestTimerHandlingDelegate delegate; + SetDelegate(&delegate); + + EXPECT_TRUE(OpenDocument("bug_679649.pdf")); + FPDF_PAGE page = LoadPage(0); + EXPECT_TRUE(page); + + delegate.SetFailNextTimer(); + DoOpenActions(); + delegate.AdvanceTime(2000); + UnloadPage(page); + + const auto& alerts = delegate.GetAlerts(); + EXPECT_EQ(0u, alerts.size()); +} + +TEST_F(FPDFFormFillEmbeddertest, BUG_707673) { + EmbedderTestTimerHandlingDelegate delegate; + SetDelegate(&delegate); + + EXPECT_TRUE(OpenDocument("bug_707673.pdf")); + FPDF_PAGE page = LoadPage(0); + EXPECT_TRUE(page); + + DoOpenActions(); + FORM_OnLButtonDown(form_handle(), page, 0, 140, 590); + FORM_OnLButtonUp(form_handle(), page, 0, 140, 590); + delegate.AdvanceTime(1000); + UnloadPage(page); + + const auto& alerts = delegate.GetAlerts(); + EXPECT_EQ(0u, alerts.size()); +} + +TEST_F(FPDFFormFillEmbeddertest, BUG_765384) { + EXPECT_TRUE(OpenDocument("bug_765384.pdf")); + FPDF_PAGE page = LoadPage(0); + EXPECT_TRUE(page); + + DoOpenActions(); + FORM_OnLButtonDown(form_handle(), page, 0, 140, 590); + FORM_OnLButtonUp(form_handle(), page, 0, 140, 590); + UnloadPage(page); +} + +#endif // PDF_ENABLE_V8 + +TEST_F(FPDFFormFillEmbeddertest, FormText) { +#if _FX_PLATFORM_ == _FX_PLATFORM_APPLE_ + const char md5_1[] = "5f11dbe575fe197a37c3fb422559f8ff"; + const char md5_2[] = "35b1a4b679eafc749a0b6fda750c0e8d"; + const char md5_3[] = "65c64a7c355388f719a752aa1e23f6fe"; +#else + const char md5_1[] = "b890950d4b9bc163b1a96797f3004b53"; + const char md5_2[] = "11487d5597599a26e8912b9c1d9422cb"; + const char md5_3[] = "bffe0ecea9a533f217047ee41d6be466"; +#endif + { + EXPECT_TRUE(OpenDocument("text_form.pdf")); + FPDF_PAGE page = LoadPage(0); + ASSERT_TRUE(page); + std::unique_ptr bitmap1 = RenderLoadedPage(page); + CompareBitmap(bitmap1.get(), 300, 300, md5_1); + + // Click on the textfield + EXPECT_EQ(FPDF_FORMFIELD_TEXTFIELD, + FPDFPage_HasFormFieldAtPoint(form_handle(), page, 120.0, 120.0)); + FORM_OnMouseMove(form_handle(), page, 0, 120.0, 120.0); + FORM_OnLButtonDown(form_handle(), page, 0, 120.0, 120.0); + FORM_OnLButtonUp(form_handle(), page, 0, 120.0, 120.0); + + // Write "ABC" + FORM_OnChar(form_handle(), page, 65, 0); + FORM_OnChar(form_handle(), page, 66, 0); + FORM_OnChar(form_handle(), page, 67, 0); + std::unique_ptr bitmap2 = RenderLoadedPage(page); + CompareBitmap(bitmap2.get(), 300, 300, md5_2); + + // Take out focus by clicking out of the textfield + FORM_OnMouseMove(form_handle(), page, 0, 15.0, 15.0); + FORM_OnLButtonDown(form_handle(), page, 0, 15.0, 15.0); + FORM_OnLButtonUp(form_handle(), page, 0, 15.0, 15.0); + std::unique_ptr bitmap3 = RenderLoadedPage(page); + CompareBitmap(bitmap3.get(), 300, 300, md5_3); + + EXPECT_TRUE(FPDF_SaveAsCopy(document(), this, 0)); + + // Close page + UnloadPage(page); + } + // Check saved document + VerifySavedDocument(300, 300, md5_3); +} + +TEST_F(FPDFFormFillEmbeddertest, HasFormInfoNone) { + EXPECT_TRUE(OpenDocument("hello_world.pdf")); + EXPECT_EQ(FORMTYPE_NONE, FPDF_GetFormType(document_)); +} + +TEST_F(FPDFFormFillEmbeddertest, HasFormInfoAcroForm) { + EXPECT_TRUE(OpenDocument("text_form.pdf")); + EXPECT_EQ(FORMTYPE_ACRO_FORM, FPDF_GetFormType(document_)); +} + +TEST_F(FPDFFormFillEmbeddertest, HasFormInfoXFAFull) { + EXPECT_TRUE(OpenDocument("simple_xfa.pdf")); + EXPECT_EQ(FORMTYPE_XFA_FULL, FPDF_GetFormType(document_)); +} + +TEST_F(FPDFFormFillEmbeddertest, HasFormInfoXFAForeground) { + EXPECT_TRUE(OpenDocument("bug_216.pdf")); + EXPECT_EQ(FORMTYPE_XFA_FOREGROUND, FPDF_GetFormType(document_)); +} + +TEST_F(FPDFFormFillTextFormEmbeddertest, GetSelectedTextEmptyAndBasicKeyboard) { + // Test empty selection. + CheckSelection(L""); + + // Test basic selection. + TypeTextIntoTextField(3, RegularFormBegin()); + SelectTextWithKeyboard(3, FWL_VKEY_Left, RegularFormAtX(123.0)); + CheckSelection(L"ABC"); +} + +TEST_F(FPDFFormFillTextFormEmbeddertest, GetSelectedTextEmptyAndBasicMouse) { + // Test empty selection. + CheckSelection(L""); + + // Test basic selection. + TypeTextIntoTextField(3, RegularFormBegin()); + SelectTextWithMouse(RegularFormAtX(125.0), RegularFormBegin()); + CheckSelection(L"ABC"); +} + +TEST_F(FPDFFormFillTextFormEmbeddertest, GetSelectedTextFragmentsKeyBoard) { + TypeTextIntoTextField(12, RegularFormBegin()); + + // Test selecting first character in forward direction. + SelectTextWithKeyboard(1, FWL_VKEY_Right, RegularFormBegin()); + CheckSelection(L"A"); + + // Test selecting entire long string in backwards direction. + SelectTextWithKeyboard(12, FWL_VKEY_Left, RegularFormEnd()); + CheckSelection(L"ABCDEFGHIJKL"); + + // Test selecting middle section in backwards direction. + SelectTextWithKeyboard(6, FWL_VKEY_Left, RegularFormAtX(170.0)); + CheckSelection(L"DEFGHI"); + + // Test selecting middle selection in forward direction. + SelectTextWithKeyboard(6, FWL_VKEY_Right, RegularFormAtX(125.0)); + CheckSelection(L"DEFGHI"); + + // Test selecting last character in backwards direction. + SelectTextWithKeyboard(1, FWL_VKEY_Left, RegularFormEnd()); + CheckSelection(L"L"); +} + +TEST_F(FPDFFormFillTextFormEmbeddertest, GetSelectedTextFragmentsMouse) { + TypeTextIntoTextField(12, RegularFormBegin()); + + // Test selecting first character in forward direction. + SelectTextWithMouse(RegularFormBegin(), RegularFormAtX(106.0)); + CheckSelection(L"A"); + + // Test selecting entire long string in backwards direction. + SelectAllRegularFormTextWithMouse(); + CheckSelection(L"ABCDEFGHIJKL"); + + // Test selecting middle section in backwards direction. + SelectTextWithMouse(RegularFormAtX(170.0), RegularFormAtX(125.0)); + CheckSelection(L"DEFGHI"); + + // Test selecting middle selection in forward direction. + SelectTextWithMouse(RegularFormAtX(125.0), RegularFormAtX(170.0)); + CheckSelection(L"DEFGHI"); + + // Test selecting last character in backwards direction. + SelectTextWithMouse(RegularFormEnd(), RegularFormAtX(186.0)); + CheckSelection(L"L"); +} + +TEST_F(FPDFFormFillComboBoxFormEmbeddertest, + GetSelectedTextEmptyAndBasicNormalComboBox) { + // Test empty selection. + CheckSelection(L""); + + // Non-editable comboboxes don't allow selection with keyboard. + SelectTextWithMouse(NonEditableFormBegin(), NonEditableFormAtX(142.0)); + CheckSelection(L"Banana"); + + // Select other another provided option. + SelectNonEditableFormOption(0); + CheckSelection(L"Apple"); +} + +TEST_F(FPDFFormFillComboBoxFormEmbeddertest, + GetSelectedTextEmptyAndBasicEditableComboBoxKeyboard) { + // Test empty selection. + CheckSelection(L""); + + // Test basic selection of text within user editable combobox using keyboard. + TypeTextIntoTextField(3, EditableFormBegin()); + SelectTextWithKeyboard(3, FWL_VKEY_Left, EditableFormAtX(128.0)); + CheckSelection(L"ABC"); + + // Select a provided option. + SelectEditableFormOption(1); + CheckSelection(L"Bar"); +} + +TEST_F(FPDFFormFillComboBoxFormEmbeddertest, + GetSelectedTextEmptyAndBasicEditableComboBoxMouse) { + // Test empty selection. + CheckSelection(L""); + + // Test basic selection of text within user editable combobox using mouse. + TypeTextIntoTextField(3, EditableFormBegin()); + SelectTextWithMouse(EditableFormAtX(128.0), EditableFormBegin()); + CheckSelection(L"ABC"); + + // Select a provided option. + SelectEditableFormOption(2); + CheckSelection(L"Qux"); +} + +TEST_F(FPDFFormFillComboBoxFormEmbeddertest, + GetSelectedTextFragmentsNormalComboBox) { + // Test selecting first character in forward direction. + SelectTextWithMouse(NonEditableFormBegin(), NonEditableFormAtX(107.0)); + CheckSelection(L"B"); + + // Test selecting entire string in backwards direction. + SelectTextWithMouse(NonEditableFormAtX(142.0), NonEditableFormBegin()); + CheckSelection(L"Banana"); + + // Test selecting middle section in backwards direction. + SelectTextWithMouse(NonEditableFormAtX(135.0), NonEditableFormAtX(117.0)); + CheckSelection(L"nan"); + + // Test selecting middle section in forward direction. + SelectTextWithMouse(NonEditableFormAtX(117.0), NonEditableFormAtX(135.0)); + CheckSelection(L"nan"); + + // Test selecting last character in backwards direction. + SelectTextWithMouse(NonEditableFormAtX(142.0), NonEditableFormAtX(138.0)); + CheckSelection(L"a"); + + // Select another option and then reset selection as first three chars. + SelectNonEditableFormOption(2); + CheckSelection(L"Cherry"); + SelectTextWithMouse(NonEditableFormBegin(), NonEditableFormAtX(122.0)); + CheckSelection(L"Che"); +} + +TEST_F(FPDFFormFillComboBoxFormEmbeddertest, + GetSelectedTextFragmentsEditableComboBoxKeyboard) { + TypeTextIntoTextField(10, EditableFormBegin()); + + // Test selecting first character in forward direction. + SelectTextWithKeyboard(1, FWL_VKEY_Right, EditableFormBegin()); + CheckSelection(L"A"); + + // Test selecting entire long string in backwards direction. + SelectTextWithKeyboard(10, FWL_VKEY_Left, EditableFormEnd()); + CheckSelection(L"ABCDEFGHIJ"); + + // Test selecting middle section in backwards direction. + SelectTextWithKeyboard(5, FWL_VKEY_Left, EditableFormAtX(168.0)); + CheckSelection(L"DEFGH"); + + // Test selecting middle selection in forward direction. + SelectTextWithKeyboard(5, FWL_VKEY_Right, EditableFormAtX(127.0)); + CheckSelection(L"DEFGH"); + + // Test selecting last character in backwards direction. + SelectTextWithKeyboard(1, FWL_VKEY_Left, EditableFormEnd()); + CheckSelection(L"J"); + + // Select a provided option and then reset selection as first two chars. + SelectEditableFormOption(0); + CheckSelection(L"Foo"); + SelectTextWithKeyboard(2, FWL_VKEY_Right, EditableFormBegin()); + CheckSelection(L"Fo"); +} + +TEST_F(FPDFFormFillComboBoxFormEmbeddertest, + GetSelectedTextFragmentsEditableComboBoxMouse) { + TypeTextIntoTextField(10, EditableFormBegin()); + + // Test selecting first character in forward direction. + SelectTextWithMouse(EditableFormBegin(), EditableFormAtX(107.0)); + CheckSelection(L"A"); + + // Test selecting entire long string in backwards direction. + SelectAllEditableFormTextWithMouse(); + CheckSelection(L"ABCDEFGHIJ"); + + // Test selecting middle section in backwards direction. + SelectTextWithMouse(EditableFormAtX(168.0), EditableFormAtX(127.0)); + CheckSelection(L"DEFGH"); + + // Test selecting middle selection in forward direction. + SelectTextWithMouse(EditableFormAtX(127.0), EditableFormAtX(168.0)); + CheckSelection(L"DEFGH"); + + // Test selecting last character in backwards direction. + SelectTextWithMouse(EditableFormEnd(), EditableFormAtX(174.0)); + CheckSelection(L"J"); +} + +TEST_F(FPDFFormFillTextFormEmbeddertest, DeleteTextFieldEntireSelection) { + // Select entire contents of text field. + TypeTextIntoTextField(12, RegularFormBegin()); + SelectAllRegularFormTextWithMouse(); + CheckSelection(L"ABCDEFGHIJKL"); + + // Test deleting current text selection. Select what remains after deletion to + // check that remaining text is as expected. + FORM_ReplaceSelection(form_handle(), page(), nullptr); + + SelectTextWithKeyboard(12, FWL_VKEY_Left, RegularFormEnd()); + CheckSelection(L""); +} + +TEST_F(FPDFFormFillTextFormEmbeddertest, DeleteTextFieldSelectionMiddle) { + // Select middle section of text. + TypeTextIntoTextField(12, RegularFormBegin()); + SelectTextWithMouse(RegularFormAtX(170.0), RegularFormAtX(125.0)); + CheckSelection(L"DEFGHI"); + + // Test deleting current text selection. Select what remains after deletion to + // check that remaining text is as expected. + FORM_ReplaceSelection(form_handle(), page(), nullptr); + SelectTextWithKeyboard(12, FWL_VKEY_Left, RegularFormEnd()); + CheckSelection(L"ABCJKL"); +} + +TEST_F(FPDFFormFillTextFormEmbeddertest, DeleteTextFieldSelectionLeft) { + // Select first few characters of text. + TypeTextIntoTextField(12, RegularFormBegin()); + SelectTextWithMouse(RegularFormBegin(), RegularFormAtX(132.0)); + CheckSelection(L"ABCD"); + + // Test deleting current text selection. Select what remains after deletion to + // check that remaining text is as expected. + FORM_ReplaceSelection(form_handle(), page(), nullptr); + SelectTextWithKeyboard(12, FWL_VKEY_Left, RegularFormEnd()); + CheckSelection(L"EFGHIJKL"); +} + +TEST_F(FPDFFormFillTextFormEmbeddertest, DeleteTextFieldSelectionRight) { + // Select last few characters of text. + TypeTextIntoTextField(12, RegularFormBegin()); + SelectTextWithMouse(RegularFormEnd(), RegularFormAtX(165.0)); + CheckSelection(L"IJKL"); + + // Test deleting current text selection. Select what remains after deletion to + // check that remaining text is as expected. + FORM_ReplaceSelection(form_handle(), page(), nullptr); + SelectTextWithKeyboard(12, FWL_VKEY_Left, RegularFormEnd()); + CheckSelection(L"ABCDEFGH"); +} + +TEST_F(FPDFFormFillTextFormEmbeddertest, DeleteEmptyTextFieldSelection) { + // Do not select text. + TypeTextIntoTextField(12, RegularFormBegin()); + CheckSelection(L""); + + // Test that attempt to delete empty text selection has no effect. + FORM_ReplaceSelection(form_handle(), page(), nullptr); + SelectTextWithKeyboard(12, FWL_VKEY_Left, RegularFormEnd()); + CheckSelection(L"ABCDEFGHIJKL"); +} + +TEST_F(FPDFFormFillComboBoxFormEmbeddertest, + DeleteEditableComboBoxEntireSelection) { + // Select entire contents of user-editable combobox text field. + TypeTextIntoTextField(10, EditableFormBegin()); + SelectAllEditableFormTextWithMouse(); + CheckSelection(L"ABCDEFGHIJ"); + + // Test deleting current text selection. Select what remains after deletion to + // check that remaining text is as expected. + FORM_ReplaceSelection(form_handle(), page(), nullptr); + SelectAllEditableFormTextWithMouse(); + CheckSelection(L""); +} + +TEST_F(FPDFFormFillComboBoxFormEmbeddertest, + DeleteEditableComboBoxSelectionMiddle) { + // Select middle section of text. + TypeTextIntoTextField(10, EditableFormBegin()); + SelectTextWithMouse(EditableFormAtX(168.0), EditableFormAtX(127.0)); + CheckSelection(L"DEFGH"); + + // Test deleting current text selection. Select what remains after deletion to + // check that remaining text is as expected. + FORM_ReplaceSelection(form_handle(), page(), nullptr); + SelectAllEditableFormTextWithMouse(); + CheckSelection(L"ABCIJ"); +} + +TEST_F(FPDFFormFillComboBoxFormEmbeddertest, + DeleteEditableComboBoxSelectionLeft) { + // Select first few characters of text. + TypeTextIntoTextField(10, EditableFormBegin()); + SelectTextWithMouse(EditableFormBegin(), EditableFormAtX(132.0)); + CheckSelection(L"ABCD"); + + // Test deleting current text selection. Select what remains after deletion to + // check that remaining text is as expected. + FORM_ReplaceSelection(form_handle(), page(), nullptr); + SelectAllEditableFormTextWithMouse(); + CheckSelection(L"EFGHIJ"); +} + +TEST_F(FPDFFormFillComboBoxFormEmbeddertest, + DeleteEditableComboBoxSelectionRight) { + // Select last few characters of text. + TypeTextIntoTextField(10, EditableFormBegin()); + SelectTextWithMouse(EditableFormEnd(), EditableFormAtX(152.0)); + CheckSelection(L"GHIJ"); + + // Test deleting current text selection. Select what remains after deletion to + // check that remaining text is as expected. + FORM_ReplaceSelection(form_handle(), page(), nullptr); + SelectAllEditableFormTextWithMouse(); + CheckSelection(L"ABCDEF"); +} + +TEST_F(FPDFFormFillComboBoxFormEmbeddertest, + DeleteEmptyEditableComboBoxSelection) { + // Do not select text. + TypeTextIntoTextField(10, EditableFormBegin()); + CheckSelection(L""); + + // Test that attempt to delete empty text selection has no effect. + FORM_ReplaceSelection(form_handle(), page(), nullptr); + SelectAllEditableFormTextWithMouse(); + CheckSelection(L"ABCDEFGHIJ"); +} + +TEST_F(FPDFFormFillTextFormEmbeddertest, InsertTextInEmptyTextField) { + ClickOnFormFieldAtPoint(RegularFormBegin()); + + // Test inserting text into empty text field. + std::unique_ptr text_to_insert = + GetFPDFWideString(L"Hello"); + FORM_ReplaceSelection(form_handle(), page(), text_to_insert.get()); + + // Select entire contents of text field to check that insertion worked + // as expected. + SelectAllRegularFormTextWithMouse(); + CheckSelection(L"Hello"); +} + +TEST_F(FPDFFormFillTextFormEmbeddertest, InsertTextInPopulatedTextFieldLeft) { + TypeTextIntoTextField(8, RegularFormBegin()); + + // Click on the leftmost part of the text field. + ClickOnFormFieldAtPoint(RegularFormBegin()); + + // Test inserting text in front of existing text in text field. + std::unique_ptr text_to_insert = + GetFPDFWideString(L"Hello"); + FORM_ReplaceSelection(form_handle(), page(), text_to_insert.get()); + + // Select entire contents of text field to check that insertion worked + // as expected. + SelectAllRegularFormTextWithMouse(); + CheckSelection(L"HelloABCDEFGH"); +} + +TEST_F(FPDFFormFillTextFormEmbeddertest, InsertTextInPopulatedTextFieldMiddle) { + TypeTextIntoTextField(8, RegularFormBegin()); + + // Click on the middle of the text field. + ClickOnFormFieldAtPoint(RegularFormAtX(134.0)); + + // Test inserting text in the middle of existing text in text field. + std::unique_ptr text_to_insert = + GetFPDFWideString(L"Hello"); + FORM_ReplaceSelection(form_handle(), page(), text_to_insert.get()); + + // Select entire contents of text field to check that insertion worked + // as expected. + SelectAllRegularFormTextWithMouse(); + CheckSelection(L"ABCDHelloEFGH"); +} + +TEST_F(FPDFFormFillTextFormEmbeddertest, InsertTextInPopulatedTextFieldRight) { + TypeTextIntoTextField(8, RegularFormBegin()); + + // Click on the rightmost part of the text field. + ClickOnFormFieldAtPoint(RegularFormAtX(166.0)); + + // Test inserting text behind existing text in text field. + std::unique_ptr text_to_insert = + GetFPDFWideString(L"Hello"); + FORM_ReplaceSelection(form_handle(), page(), text_to_insert.get()); + + // Select entire contents of text field to check that insertion worked + // as expected. + SelectAllRegularFormTextWithMouse(); + CheckSelection(L"ABCDEFGHHello"); +} + +TEST_F(FPDFFormFillTextFormEmbeddertest, + InsertTextAndReplaceSelectionInPopulatedTextFieldWhole) { + TypeTextIntoTextField(12, RegularFormBegin()); + + // Select entire string in text field. + SelectTextWithKeyboard(12, FWL_VKEY_Left, RegularFormEnd()); + CheckSelection(L"ABCDEFGHIJKL"); + + // Test replacing text selection with text to be inserted. + std::unique_ptr text_to_insert = + GetFPDFWideString(L"Hello"); + FORM_ReplaceSelection(form_handle(), page(), text_to_insert.get()); + + // Select entire contents of text field to check that insertion worked + // as expected. + SelectAllRegularFormTextWithMouse(); + CheckSelection(L"Hello"); +} + +TEST_F(FPDFFormFillTextFormEmbeddertest, + InsertTextAndReplaceSelectionInPopulatedTextFieldLeft) { + TypeTextIntoTextField(12, RegularFormBegin()); + + // Select left portion of string in text field. + SelectTextWithKeyboard(6, FWL_VKEY_Left, RegularFormAtX(148.0)); + CheckSelection(L"ABCDEF"); + + // Test replacing text selection with text to be inserted. + std::unique_ptr text_to_insert = + GetFPDFWideString(L"Hello"); + FORM_ReplaceSelection(form_handle(), page(), text_to_insert.get()); + + // Select entire contents of text field to check that insertion worked + // as expected. + SelectAllRegularFormTextWithMouse(); + CheckSelection(L"HelloGHIJKL"); +} + +TEST_F(FPDFFormFillTextFormEmbeddertest, + InsertTextAndReplaceSelectionInPopulatedTextFieldMiddle) { + TypeTextIntoTextField(12, RegularFormBegin()); + + // Select middle portion of string in text field. + SelectTextWithKeyboard(6, FWL_VKEY_Left, RegularFormAtX(171.0)); + CheckSelection(L"DEFGHI"); + + // Test replacing text selection with text to be inserted. + std::unique_ptr text_to_insert = + GetFPDFWideString(L"Hello"); + FORM_ReplaceSelection(form_handle(), page(), text_to_insert.get()); + + // Select entire contents of text field to check that insertion worked + // as expected. + SelectAllRegularFormTextWithMouse(); + CheckSelection(L"ABCHelloJKL"); +} + +TEST_F(FPDFFormFillTextFormEmbeddertest, + InsertTextAndReplaceSelectionInPopulatedTextFieldRight) { + TypeTextIntoTextField(12, RegularFormBegin()); + + // Select right portion of string in text field. + SelectTextWithKeyboard(6, FWL_VKEY_Left, RegularFormEnd()); + CheckSelection(L"GHIJKL"); + + // Test replacing text selection with text to be inserted. + std::unique_ptr text_to_insert = + GetFPDFWideString(L"Hello"); + FORM_ReplaceSelection(form_handle(), page(), text_to_insert.get()); + + // Select entire contents of text field to check that insertion worked + // as expected. + SelectAllRegularFormTextWithMouse(); + CheckSelection(L"ABCDEFHello"); +} + +TEST_F(FPDFFormFillComboBoxFormEmbeddertest, + InsertTextInEmptyEditableComboBox) { + ClickOnFormFieldAtPoint(EditableFormBegin()); + + // Test inserting text into empty user-editable combobox. + std::unique_ptr text_to_insert = + GetFPDFWideString(L"Hello"); + FORM_ReplaceSelection(form_handle(), page(), text_to_insert.get()); + + // Select entire contents of user-editable combobox text field to check that + // insertion worked as expected. + SelectAllEditableFormTextWithMouse(); + CheckSelection(L"Hello"); +} + +TEST_F(FPDFFormFillComboBoxFormEmbeddertest, + InsertTextInPopulatedEditableComboBoxLeft) { + TypeTextIntoTextField(6, EditableFormBegin()); + + // Click on the leftmost part of the user-editable combobox. + ClickOnFormFieldAtPoint(EditableFormBegin()); + + // Test inserting text in front of existing text in user-editable combobox. + std::unique_ptr text_to_insert = + GetFPDFWideString(L"Hello"); + FORM_ReplaceSelection(form_handle(), page(), text_to_insert.get()); + + // Select entire contents of user-editable combobox text field to check that + // insertion worked as expected. + SelectAllEditableFormTextWithMouse(); + CheckSelection(L"HelloABCDEF"); +} + +TEST_F(FPDFFormFillComboBoxFormEmbeddertest, + InsertTextInPopulatedEditableComboBoxMiddle) { + TypeTextIntoTextField(6, EditableFormBegin()); + + // Click on the middle of the user-editable combobox. + ClickOnFormFieldAtPoint(EditableFormAtX(126.0)); + + // Test inserting text in the middle of existing text in user-editable + // combobox. + std::unique_ptr text_to_insert = + GetFPDFWideString(L"Hello"); + FORM_ReplaceSelection(form_handle(), page(), text_to_insert.get()); + + // Select entire contents of user-editable combobox text field to check that + // insertion worked as expected. + SelectAllEditableFormTextWithMouse(); + CheckSelection(L"ABCHelloDEF"); +} + +TEST_F(FPDFFormFillComboBoxFormEmbeddertest, + InsertTextInPopulatedEditableComboBoxRight) { + TypeTextIntoTextField(6, EditableFormBegin()); + + // Click on the rightmost part of the user-editable combobox. + ClickOnFormFieldAtPoint(EditableFormEnd()); + + // Test inserting text behind existing text in user-editable combobox. + std::unique_ptr text_to_insert = + GetFPDFWideString(L"Hello"); + FORM_ReplaceSelection(form_handle(), page(), text_to_insert.get()); + + // Select entire contents of user-editable combobox text field to check that + // insertion worked as expected. + SelectAllEditableFormTextWithMouse(); + CheckSelection(L"ABCDEFHello"); +} + +TEST_F(FPDFFormFillComboBoxFormEmbeddertest, + InsertTextAndReplaceSelectionInPopulatedEditableComboBoxWhole) { + TypeTextIntoTextField(10, EditableFormBegin()); + + // Select entire string in user-editable combobox. + SelectTextWithKeyboard(10, FWL_VKEY_Left, EditableFormEnd()); + CheckSelection(L"ABCDEFGHIJ"); + + // Test replacing text selection with text to be inserted. + std::unique_ptr text_to_insert = + GetFPDFWideString(L"Hello"); + FORM_ReplaceSelection(form_handle(), page(), text_to_insert.get()); + + // Select entire contents of user-editable combobox text field to check that + // insertion worked as expected. + SelectAllEditableFormTextWithMouse(); + CheckSelection(L"Hello"); +} + +TEST_F(FPDFFormFillComboBoxFormEmbeddertest, + InsertTextAndReplaceSelectionInPopulatedEditableComboBoxLeft) { + TypeTextIntoTextField(10, EditableFormBegin()); + + // Select left portion of string in user-editable combobox. + SelectTextWithKeyboard(5, FWL_VKEY_Left, EditableFormAtX(142.0)); + CheckSelection(L"ABCDE"); + + // Test replacing text selection with text to be inserted. + std::unique_ptr text_to_insert = + GetFPDFWideString(L"Hello"); + FORM_ReplaceSelection(form_handle(), page(), text_to_insert.get()); + + // Select entire contents of user-editable combobox text field to check that + // insertion worked as expected. + SelectAllEditableFormTextWithMouse(); + CheckSelection(L"HelloFGHIJ"); +} + +TEST_F(FPDFFormFillComboBoxFormEmbeddertest, + InsertTextAndReplaceSelectionInPopulatedEditableComboBoxMiddle) { + TypeTextIntoTextField(10, EditableFormBegin()); + + // Select middle portion of string in user-editable combobox. + SelectTextWithKeyboard(5, FWL_VKEY_Left, EditableFormAtX(167.0)); + CheckSelection(L"DEFGH"); + + // Test replacing text selection with text to be inserted. + std::unique_ptr text_to_insert = + GetFPDFWideString(L"Hello"); + FORM_ReplaceSelection(form_handle(), page(), text_to_insert.get()); + + // Select entire contents of user-editable combobox text field to check that + // insertion worked as expected. + SelectAllEditableFormTextWithMouse(); + CheckSelection(L"ABCHelloIJ"); +} + +TEST_F(FPDFFormFillComboBoxFormEmbeddertest, + InsertTextAndReplaceSelectionInPopulatedEditableComboBoxRight) { + TypeTextIntoTextField(10, EditableFormBegin()); + + // Select right portion of string in user-editable combobox. + SelectTextWithKeyboard(5, FWL_VKEY_Left, EditableFormEnd()); + CheckSelection(L"FGHIJ"); + + // Test replacing text selection with text to be inserted. + std::unique_ptr text_to_insert = + GetFPDFWideString(L"Hello"); + FORM_ReplaceSelection(form_handle(), page(), text_to_insert.get()); + + // Select entire contents of user-editable combobox text field to check that + // insertion worked as expected. + SelectAllEditableFormTextWithMouse(); + CheckSelection(L"ABCDEHello"); +} + +TEST_F(FPDFFormFillTextFormEmbeddertest, + InsertTextInEmptyCharLimitTextFieldOverflow) { + // Click on the textfield. + ClickOnFormFieldAtPoint(CharLimitFormEnd()); + + // Delete pre-filled contents of text field with char limit. + SelectAllCharLimitFormTextWithMouse(); + CheckSelection(L"Elephant"); + FORM_ReplaceSelection(form_handle(), page(), nullptr); + + // Test inserting text into now empty text field so text to be inserted + // exceeds the char limit and is cut off. + std::unique_ptr text_to_insert = + GetFPDFWideString(L"Hippopotamus"); + FORM_ReplaceSelection(form_handle(), page(), text_to_insert.get()); + + // Select entire contents of text field to check that insertion worked + // as expected. + SelectAllCharLimitFormTextWithMouse(); + CheckSelection(L"Hippopotam"); +} + +TEST_F(FPDFFormFillTextFormEmbeddertest, + InsertTextInEmptyCharLimitTextFieldFit) { + // Click on the textfield. + ClickOnFormFieldAtPoint(CharLimitFormEnd()); + + // Delete pre-filled contents of text field with char limit. + SelectAllCharLimitFormTextWithMouse(); + CheckSelection(L"Elephant"); + FORM_ReplaceSelection(form_handle(), page(), nullptr); + + // Test inserting text into now empty text field so text to be inserted + // exceeds the char limit and is cut off. + std::unique_ptr text_to_insert = + GetFPDFWideString(L"Zebra"); + FORM_ReplaceSelection(form_handle(), page(), text_to_insert.get()); + + // Select entire contents of text field to check that insertion worked + // as expected. + SelectAllCharLimitFormTextWithMouse(); + CheckSelection(L"Zebra"); +} + +TEST_F(FPDFFormFillTextFormEmbeddertest, + InsertTextInPopulatedCharLimitTextFieldLeft) { + // Click on the leftmost part of the text field. + ClickOnFormFieldAtPoint(CharLimitFormBegin()); + + // Test inserting text in front of existing text in text field. + std::unique_ptr text_to_insert = + GetFPDFWideString(L"Hippopotamus"); + FORM_ReplaceSelection(form_handle(), page(), text_to_insert.get()); + + // Select entire contents of text field to check that insertion worked + // as expected. + SelectAllCharLimitFormTextWithMouse(); + CheckSelection(L"HiElephant"); +} + +TEST_F(FPDFFormFillTextFormEmbeddertest, + InsertTextInPopulatedCharLimitTextFieldMiddle) { + TypeTextIntoTextField(8, RegularFormBegin()); + + // Click on the middle of the text field. + ClickOnFormFieldAtPoint(CharLimitFormAtX(134.0)); + + // Test inserting text in the middle of existing text in text field. + std::unique_ptr text_to_insert = + GetFPDFWideString(L"Hippopotamus"); + FORM_ReplaceSelection(form_handle(), page(), text_to_insert.get()); + + // Select entire contents of text field to check that insertion worked + // as expected. + SelectAllCharLimitFormTextWithMouse(); + CheckSelection(L"ElephHiant"); +} + +TEST_F(FPDFFormFillTextFormEmbeddertest, + InsertTextInPopulatedCharLimitTextFieldRight) { + TypeTextIntoTextField(8, RegularFormBegin()); + + // Click on the rightmost part of the text field. + ClickOnFormFieldAtPoint(CharLimitFormAtX(166.0)); + + // Test inserting text behind existing text in text field. + std::unique_ptr text_to_insert = + GetFPDFWideString(L"Hippopotamus"); + FORM_ReplaceSelection(form_handle(), page(), text_to_insert.get()); + + // Select entire contents of text field to check that insertion worked + // as expected. + SelectAllCharLimitFormTextWithMouse(); + CheckSelection(L"ElephantHi"); +} + +TEST_F(FPDFFormFillTextFormEmbeddertest, + InsertTextAndReplaceSelectionInPopulatedCharLimitTextFieldWhole) { + TypeTextIntoTextField(12, RegularFormBegin()); + + // Select entire string in text field. + SelectTextWithKeyboard(12, FWL_VKEY_Left, CharLimitFormEnd()); + CheckSelection(L"Elephant"); + + // Test replacing text selection with text to be inserted. + std::unique_ptr text_to_insert = + GetFPDFWideString(L"Hippopotamus"); + FORM_ReplaceSelection(form_handle(), page(), text_to_insert.get()); + + // Select entire contents of text field to check that insertion worked + // as expected. + SelectAllCharLimitFormTextWithMouse(); + CheckSelection(L"Hippopotam"); +} + +TEST_F(FPDFFormFillTextFormEmbeddertest, + InsertTextAndReplaceSelectionInPopulatedCharLimitTextFieldLeft) { + TypeTextIntoTextField(12, RegularFormBegin()); + + // Select left portion of string in text field. + SelectTextWithKeyboard(4, FWL_VKEY_Left, CharLimitFormAtX(122.0)); + CheckSelection(L"Elep"); + + // Test replacing text selection with text to be inserted. + std::unique_ptr text_to_insert = + GetFPDFWideString(L"Hippopotamus"); + FORM_ReplaceSelection(form_handle(), page(), text_to_insert.get()); + + // Select entire contents of text field to check that insertion worked + // as expected. + SelectAllCharLimitFormTextWithMouse(); + CheckSelection(L"Hippophant"); +} + +TEST_F(FPDFFormFillTextFormEmbeddertest, + InsertTextAndReplaceSelectionInPopulatedCharLimitTextFieldMiddle) { + TypeTextIntoTextField(12, RegularFormBegin()); + + // Select middle portion of string in text field. + SelectTextWithKeyboard(4, FWL_VKEY_Left, CharLimitFormAtX(136.0)); + CheckSelection(L"epha"); + + // Test replacing text selection with text to be inserted. + std::unique_ptr text_to_insert = + GetFPDFWideString(L"Hippopotamus"); + FORM_ReplaceSelection(form_handle(), page(), text_to_insert.get()); + + // Select entire contents of text field to check that insertion worked + // as expected. + SelectAllCharLimitFormTextWithMouse(); + CheckSelection(L"ElHippopnt"); +} + +TEST_F(FPDFFormFillTextFormEmbeddertest, + InsertTextAndReplaceSelectionInPopulatedCharLimitTextFieldRight) { + TypeTextIntoTextField(12, RegularFormBegin()); + + // Select right portion of string in text field. + SelectTextWithKeyboard(4, FWL_VKEY_Left, CharLimitFormAtX(152.0)); + CheckSelection(L"hant"); + + // Test replacing text selection with text to be inserted. + std::unique_ptr text_to_insert = + GetFPDFWideString(L"Hippopotamus"); + FORM_ReplaceSelection(form_handle(), page(), text_to_insert.get()); + + // Select entire contents of text field to check that insertion worked + // as expected. + SelectAllCharLimitFormTextWithMouse(); + CheckSelection(L"ElepHippop"); +} diff --git a/fpdfsdk/fpdf_ppo_embeddertest.cpp b/fpdfsdk/fpdf_ppo_embeddertest.cpp new file mode 100644 index 0000000000..f62ca52048 --- /dev/null +++ b/fpdfsdk/fpdf_ppo_embeddertest.cpp @@ -0,0 +1,278 @@ +// Copyright 2016 PDFium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include +#include + +#include "public/cpp/fpdf_deleters.h" +#include "public/fpdf_edit.h" +#include "public/fpdf_ppo.h" +#include "public/fpdf_save.h" +#include "public/fpdfview.h" +#include "testing/embedder_test.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "testing/test_support.h" + +namespace { + +class FPDFPPOEmbeddertest : public EmbedderTest {}; + +int FakeBlockWriter(FPDF_FILEWRITE* pThis, + const void* pData, + unsigned long size) { + return size; +} + +} // namespace + +TEST_F(FPDFPPOEmbeddertest, NoViewerPreferences) { + EXPECT_TRUE(OpenDocument("hello_world.pdf")); + + FPDF_DOCUMENT output_doc = FPDF_CreateNewDocument(); + EXPECT_TRUE(output_doc); + EXPECT_FALSE(FPDF_CopyViewerPreferences(output_doc, document())); + FPDF_CloseDocument(output_doc); +} + +TEST_F(FPDFPPOEmbeddertest, ViewerPreferences) { + EXPECT_TRUE(OpenDocument("viewer_ref.pdf")); + + FPDF_DOCUMENT output_doc = FPDF_CreateNewDocument(); + EXPECT_TRUE(output_doc); + EXPECT_TRUE(FPDF_CopyViewerPreferences(output_doc, document())); + FPDF_CloseDocument(output_doc); +} + +TEST_F(FPDFPPOEmbeddertest, ImportPages) { + ASSERT_TRUE(OpenDocument("viewer_ref.pdf")); + + FPDF_PAGE page = LoadPage(0); + EXPECT_TRUE(page); + + FPDF_DOCUMENT output_doc = FPDF_CreateNewDocument(); + ASSERT_TRUE(output_doc); + EXPECT_TRUE(FPDF_CopyViewerPreferences(output_doc, document())); + EXPECT_TRUE(FPDF_ImportPages(output_doc, document(), "1", 0)); + EXPECT_EQ(1, FPDF_GetPageCount(output_doc)); + FPDF_CloseDocument(output_doc); + + UnloadPage(page); +} + +TEST_F(FPDFPPOEmbeddertest, ImportNPages) { + ASSERT_TRUE(OpenDocument("rectangles_multi_pages.pdf")); + + std::unique_ptr output_doc_2up( + FPDF_ImportNPagesToOne(document(), 612, 792, 2, 1)); + ASSERT_TRUE(output_doc_2up); + EXPECT_EQ(3, FPDF_GetPageCount(output_doc_2up.get())); + std::unique_ptr output_doc_5up( + FPDF_ImportNPagesToOne(document(), 612, 792, 5, 1)); + ASSERT_TRUE(output_doc_5up); + EXPECT_EQ(1, FPDF_GetPageCount(output_doc_5up.get())); + std::unique_ptr output_doc_8up( + FPDF_ImportNPagesToOne(document(), 792, 612, 8, 1)); + ASSERT_TRUE(output_doc_8up); + EXPECT_EQ(1, FPDF_GetPageCount(output_doc_8up.get())); + std::unique_ptr output_doc_128up( + FPDF_ImportNPagesToOne(document(), 792, 612, 128, 1)); + ASSERT_TRUE(output_doc_128up); + EXPECT_EQ(1, FPDF_GetPageCount(output_doc_128up.get())); +} + +TEST_F(FPDFPPOEmbeddertest, BadNupParams) { + ASSERT_TRUE(OpenDocument("rectangles_multi_pages.pdf")); + + FPDF_DOCUMENT output_doc_zero_row = + FPDF_ImportNPagesToOne(document(), 612, 792, 0, 3); + ASSERT_FALSE(output_doc_zero_row); + FPDF_DOCUMENT output_doc_zero_col = + FPDF_ImportNPagesToOne(document(), 612, 792, 2, 0); + ASSERT_FALSE(output_doc_zero_col); + FPDF_DOCUMENT output_doc_zero_width = + FPDF_ImportNPagesToOne(document(), 0, 792, 2, 1); + ASSERT_FALSE(output_doc_zero_width); + FPDF_DOCUMENT output_doc_zero_height = + FPDF_ImportNPagesToOne(document(), 612, 0, 7, 1); + ASSERT_FALSE(output_doc_zero_height); +} + +// TODO(Xlou): Add more tests to check output doc content of +// FPDF_ImportNPagesToOne() +TEST_F(FPDFPPOEmbeddertest, NupRenderImage) { + ASSERT_TRUE(OpenDocument("rectangles_multi_pages.pdf")); + const int kPageCount = 2; + constexpr const char* kExpectedMD5s[kPageCount] = { + "4d225b961da0f1bced7c83273e64c9b6", "fb18142190d770cfbc329d2b071aee4d"}; + std::unique_ptr output_doc_3up( + FPDF_ImportNPagesToOne(document(), 792, 612, 3, 1)); + ASSERT_TRUE(output_doc_3up); + ASSERT_EQ(kPageCount, FPDF_GetPageCount(output_doc_3up.get())); + for (int i = 0; i < kPageCount; ++i) { + std::unique_ptr page( + FPDF_LoadPage(output_doc_3up.get(), i)); + ASSERT_TRUE(page); + std::unique_ptr bitmap( + RenderPageWithFlags(page.get(), nullptr, 0)); + EXPECT_EQ(792, FPDFBitmap_GetWidth(bitmap.get())); + EXPECT_EQ(612, FPDFBitmap_GetHeight(bitmap.get())); + EXPECT_EQ(kExpectedMD5s[i], HashBitmap(bitmap.get())); + } +} + +TEST_F(FPDFPPOEmbeddertest, BadRepeatViewerPref) { + ASSERT_TRUE(OpenDocument("repeat_viewer_ref.pdf")); + + FPDF_DOCUMENT output_doc = FPDF_CreateNewDocument(); + EXPECT_TRUE(output_doc); + EXPECT_TRUE(FPDF_CopyViewerPreferences(output_doc, document())); + + FPDF_FILEWRITE writer; + writer.version = 1; + writer.WriteBlock = FakeBlockWriter; + + EXPECT_TRUE(FPDF_SaveAsCopy(output_doc, &writer, 0)); + FPDF_CloseDocument(output_doc); +} + +TEST_F(FPDFPPOEmbeddertest, BadCircularViewerPref) { + ASSERT_TRUE(OpenDocument("circular_viewer_ref.pdf")); + + FPDF_DOCUMENT output_doc = FPDF_CreateNewDocument(); + EXPECT_TRUE(output_doc); + EXPECT_TRUE(FPDF_CopyViewerPreferences(output_doc, document())); + + FPDF_FILEWRITE writer; + writer.version = 1; + writer.WriteBlock = FakeBlockWriter; + + EXPECT_TRUE(FPDF_SaveAsCopy(output_doc, &writer, 0)); + FPDF_CloseDocument(output_doc); +} + +TEST_F(FPDFPPOEmbeddertest, BadRanges) { + EXPECT_TRUE(OpenDocument("viewer_ref.pdf")); + + FPDF_PAGE page = LoadPage(0); + EXPECT_TRUE(page); + + FPDF_DOCUMENT output_doc = FPDF_CreateNewDocument(); + EXPECT_TRUE(output_doc); + EXPECT_FALSE(FPDF_ImportPages(output_doc, document(), "clams", 0)); + EXPECT_FALSE(FPDF_ImportPages(output_doc, document(), "0", 0)); + EXPECT_FALSE(FPDF_ImportPages(output_doc, document(), "42", 0)); + EXPECT_FALSE(FPDF_ImportPages(output_doc, document(), "1,2", 0)); + EXPECT_FALSE(FPDF_ImportPages(output_doc, document(), "1-2", 0)); + EXPECT_FALSE(FPDF_ImportPages(output_doc, document(), ",1", 0)); + EXPECT_FALSE(FPDF_ImportPages(output_doc, document(), "1,", 0)); + EXPECT_FALSE(FPDF_ImportPages(output_doc, document(), "1-", 0)); + EXPECT_FALSE(FPDF_ImportPages(output_doc, document(), "-1", 0)); + EXPECT_FALSE(FPDF_ImportPages(output_doc, document(), "-,0,,,1-", 0)); + FPDF_CloseDocument(output_doc); + + UnloadPage(page); +} + +TEST_F(FPDFPPOEmbeddertest, GoodRanges) { + EXPECT_TRUE(OpenDocument("viewer_ref.pdf")); + + FPDF_PAGE page = LoadPage(0); + EXPECT_TRUE(page); + + FPDF_DOCUMENT output_doc = FPDF_CreateNewDocument(); + EXPECT_TRUE(output_doc); + EXPECT_TRUE(FPDF_CopyViewerPreferences(output_doc, document())); + EXPECT_TRUE(FPDF_ImportPages(output_doc, document(), "1,1,1,1", 0)); + EXPECT_TRUE(FPDF_ImportPages(output_doc, document(), "1-1", 0)); + EXPECT_EQ(5, FPDF_GetPageCount(output_doc)); + FPDF_CloseDocument(output_doc); + + UnloadPage(page); +} + +TEST_F(FPDFPPOEmbeddertest, BUG_664284) { + EXPECT_TRUE(OpenDocument("bug_664284.pdf")); + + FPDF_PAGE page = LoadPage(0); + ASSERT_NE(nullptr, page); + + FPDF_DOCUMENT output_doc = FPDF_CreateNewDocument(); + EXPECT_TRUE(output_doc); + EXPECT_TRUE(FPDF_ImportPages(output_doc, document(), "1", 0)); + FPDF_CloseDocument(output_doc); + + UnloadPage(page); +} + +TEST_F(FPDFPPOEmbeddertest, BUG_750568) { + const char* const kHashes[] = { + "64ad08132a1c5a166768298c8a578f57", "83b83e2f6bc80707d0a917c7634140b9", + "913cd3723a451e4e46fbc2c05702d1ee", "81fb7cfd4860f855eb468f73dfeb6d60"}; + + ASSERT_TRUE(OpenDocument("bug_750568.pdf")); + ASSERT_EQ(4, FPDF_GetPageCount(document())); + + for (size_t i = 0; i < 4; ++i) { + FPDF_PAGE page = LoadPage(i); + ASSERT_TRUE(page); + + std::unique_ptr bitmap = RenderLoadedPage(page); + ASSERT_EQ(200, FPDFBitmap_GetWidth(bitmap.get())); + ASSERT_EQ(200, FPDFBitmap_GetHeight(bitmap.get())); + ASSERT_EQ(800, FPDFBitmap_GetStride(bitmap.get())); + + EXPECT_EQ(kHashes[i], HashBitmap(bitmap.get())); + UnloadPage(page); + } + + FPDF_DOCUMENT output_doc = FPDF_CreateNewDocument(); + ASSERT_TRUE(output_doc); + EXPECT_TRUE(FPDF_ImportPages(output_doc, document(), "1,2,3,4", 0)); + ASSERT_EQ(4, FPDF_GetPageCount(output_doc)); + for (size_t i = 0; i < 4; ++i) { + FPDF_PAGE page = FPDF_LoadPage(output_doc, i); + ASSERT_TRUE(page); + + std::unique_ptr bitmap = + RenderPageWithFlags(page, nullptr, 0); + ASSERT_EQ(200, FPDFBitmap_GetWidth(bitmap.get())); + ASSERT_EQ(200, FPDFBitmap_GetHeight(bitmap.get())); + ASSERT_EQ(800, FPDFBitmap_GetStride(bitmap.get())); + + EXPECT_EQ(kHashes[i], HashBitmap(bitmap.get())); + FPDF_ClosePage(page); + } + FPDF_CloseDocument(output_doc); +} + +TEST_F(FPDFPPOEmbeddertest, ImportWithZeroLengthStream) { + EXPECT_TRUE(OpenDocument("zero_length_stream.pdf")); + FPDF_PAGE page = LoadPage(0); + ASSERT_TRUE(page); + + std::unique_ptr bitmap = RenderLoadedPage(page); + ASSERT_EQ(200, FPDFBitmap_GetWidth(bitmap.get())); + ASSERT_EQ(200, FPDFBitmap_GetHeight(bitmap.get())); + ASSERT_EQ(800, FPDFBitmap_GetStride(bitmap.get())); + + std::string digest = HashBitmap(bitmap.get()); + UnloadPage(page); + + FPDF_DOCUMENT new_doc = FPDF_CreateNewDocument(); + EXPECT_TRUE(new_doc); + EXPECT_TRUE(FPDF_ImportPages(new_doc, document(), "1", 0)); + + EXPECT_EQ(1, FPDF_GetPageCount(new_doc)); + FPDF_PAGE new_page = FPDF_LoadPage(new_doc, 0); + ASSERT_NE(nullptr, new_page); + std::unique_ptr new_bitmap = + RenderPageWithFlags(new_page, nullptr, 0); + ASSERT_EQ(200, FPDFBitmap_GetWidth(new_bitmap.get())); + ASSERT_EQ(200, FPDFBitmap_GetHeight(new_bitmap.get())); + ASSERT_EQ(800, FPDFBitmap_GetStride(new_bitmap.get())); + + EXPECT_EQ(digest, HashBitmap(new_bitmap.get())); + FPDF_ClosePage(new_page); + FPDF_CloseDocument(new_doc); +} diff --git a/fpdfsdk/fpdf_save.cpp b/fpdfsdk/fpdf_save.cpp new file mode 100644 index 0000000000..da3e12fc9c --- /dev/null +++ b/fpdfsdk/fpdf_save.cpp @@ -0,0 +1,287 @@ +// Copyright 2014 PDFium Authors. All rights reserved. +// 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_save.h" + +#include +#include +#include + +#include "core/fpdfapi/edit/cpdf_creator.h" +#include "core/fpdfapi/parser/cpdf_array.h" +#include "core/fpdfapi/parser/cpdf_document.h" +#include "core/fpdfapi/parser/cpdf_reference.h" +#include "core/fpdfapi/parser/cpdf_stream_acc.h" +#include "core/fpdfapi/parser/cpdf_string.h" +#include "core/fxcrt/cfx_memorystream.h" +#include "core/fxcrt/fx_extension.h" +#include "fpdfsdk/cpdfsdk_filewriteadapter.h" +#include "fpdfsdk/cpdfsdk_helpers.h" +#include "public/fpdf_edit.h" + +#ifdef PDF_ENABLE_XFA +#include "core/fxcrt/cfx_checksumcontext.h" +#include "fpdfsdk/fpdfxfa/cpdfxfa_context.h" +#include "fpdfsdk/fpdfxfa/cxfa_fwladaptertimermgr.h" +#include "public/fpdf_formfill.h" +#include "xfa/fxfa/cxfa_eventparam.h" +#include "xfa/fxfa/cxfa_ffapp.h" +#include "xfa/fxfa/cxfa_ffdocview.h" +#include "xfa/fxfa/cxfa_ffwidgethandler.h" +#include "xfa/fxfa/cxfa_readynodeiterator.h" +#include "xfa/fxfa/parser/cxfa_object.h" +#endif + +#if _FX_OS_ == _FX_OS_ANDROID_ +#include +#else +#include +#endif + +namespace { + +#ifdef PDF_ENABLE_XFA +bool SaveXFADocumentData(CPDFXFA_Context* pContext, + std::vector>* fileList) { + if (!pContext) + return false; + + if (!pContext->ContainsXFAForm()) + return true; + + CXFA_FFDocView* pXFADocView = pContext->GetXFADocView(); + if (!pXFADocView) + return true; + + CPDF_Document* pPDFDocument = pContext->GetPDFDoc(); + if (!pPDFDocument) + return false; + + const CPDF_Dictionary* pRoot = pPDFDocument->GetRoot(); + if (!pRoot) + return false; + + CPDF_Dictionary* pAcroForm = pRoot->GetDictFor("AcroForm"); + if (!pAcroForm) + return false; + + CPDF_Object* pXFA = pAcroForm->GetObjectFor("XFA"); + if (!pXFA) + return true; + + CPDF_Array* pArray = pXFA->AsArray(); + if (!pArray) + return false; + + int size = pArray->GetCount(); + int iFormIndex = -1; + int iDataSetsIndex = -1; + int iTemplate = -1; + int iLast = size - 2; + for (int i = 0; i < size - 1; i++) { + CPDF_Object* pPDFObj = pArray->GetObjectAt(i); + if (!pPDFObj->IsString()) + continue; + if (pPDFObj->GetString() == "form") + iFormIndex = i + 1; + else if (pPDFObj->GetString() == "datasets") + iDataSetsIndex = i + 1; + else if (pPDFObj->GetString() == "template") + iTemplate = i + 1; + } + auto pChecksum = pdfium::MakeUnique(); + pChecksum->StartChecksum(); + + // template + if (iTemplate > -1) { + CPDF_Stream* pTemplateStream = pArray->GetStreamAt(iTemplate); + auto pAcc = pdfium::MakeRetain(pTemplateStream); + pAcc->LoadAllDataFiltered(); + RetainPtr pTemplate = + pdfium::MakeRetain( + const_cast(pAcc->GetData()), pAcc->GetSize(), false); + pChecksum->UpdateChecksum(pTemplate); + } + CPDF_Stream* pFormStream = nullptr; + CPDF_Stream* pDataSetsStream = nullptr; + if (iFormIndex != -1) { + // Get form CPDF_Stream + CPDF_Object* pFormPDFObj = pArray->GetObjectAt(iFormIndex); + if (pFormPDFObj->IsReference()) { + CPDF_Object* pFormDirectObj = pFormPDFObj->GetDirect(); + if (pFormDirectObj && pFormDirectObj->IsStream()) { + pFormStream = (CPDF_Stream*)pFormDirectObj; + } + } else if (pFormPDFObj->IsStream()) { + pFormStream = (CPDF_Stream*)pFormPDFObj; + } + } + + if (iDataSetsIndex != -1) { + // Get datasets CPDF_Stream + CPDF_Object* pDataSetsPDFObj = pArray->GetObjectAt(iDataSetsIndex); + if (pDataSetsPDFObj->IsReference()) { + CPDF_Reference* pDataSetsRefObj = (CPDF_Reference*)pDataSetsPDFObj; + CPDF_Object* pDataSetsDirectObj = pDataSetsRefObj->GetDirect(); + if (pDataSetsDirectObj && pDataSetsDirectObj->IsStream()) { + pDataSetsStream = (CPDF_Stream*)pDataSetsDirectObj; + } + } else if (pDataSetsPDFObj->IsStream()) { + pDataSetsStream = (CPDF_Stream*)pDataSetsPDFObj; + } + } + // L"datasets" + { + RetainPtr pDsfileWrite = + pdfium::MakeRetain(false); + CXFA_FFDoc* ffdoc = pXFADocView->GetDoc(); + if (ffdoc->SavePackage( + ToNode(ffdoc->GetXFADoc()->GetXFAObject(XFA_HASHCODE_Datasets)), + pDsfileWrite, nullptr) && + pDsfileWrite->GetSize() > 0) { + // Datasets + pChecksum->UpdateChecksum(pDsfileWrite); + pChecksum->FinishChecksum(); + auto pDataDict = pdfium::MakeUnique( + pPDFDocument->GetByteStringPool()); + if (iDataSetsIndex != -1) { + if (pDataSetsStream) { + pDataSetsStream->InitStreamFromFile(pDsfileWrite, + std::move(pDataDict)); + } + } else { + CPDF_Stream* pData = pPDFDocument->NewIndirect(); + pData->InitStreamFromFile(pDsfileWrite, std::move(pDataDict)); + iLast = pArray->GetCount() - 2; + pArray->InsertNewAt(iLast, "datasets", false); + pArray->InsertNewAt(iLast + 1, pPDFDocument, + pData->GetObjNum()); + } + fileList->push_back(std::move(pDsfileWrite)); + } + } + // L"form" + { + RetainPtr pfileWrite = + pdfium::MakeRetain(false); + + CXFA_FFDoc* ffdoc = pXFADocView->GetDoc(); + if (ffdoc->SavePackage( + ToNode(ffdoc->GetXFADoc()->GetXFAObject(XFA_HASHCODE_Form)), + pfileWrite, pChecksum.get()) && + pfileWrite->GetSize() > 0) { + auto pDataDict = pdfium::MakeUnique( + pPDFDocument->GetByteStringPool()); + if (iFormIndex != -1) { + if (pFormStream) + pFormStream->InitStreamFromFile(pfileWrite, std::move(pDataDict)); + } else { + CPDF_Stream* pData = pPDFDocument->NewIndirect(); + pData->InitStreamFromFile(pfileWrite, std::move(pDataDict)); + iLast = pArray->GetCount() - 2; + pArray->InsertNewAt(iLast, "form", false); + pArray->InsertNewAt(iLast + 1, pPDFDocument, + pData->GetObjNum()); + } + fileList->push_back(std::move(pfileWrite)); + } + } + return true; +} + +bool SendPostSaveToXFADoc(CPDFXFA_Context* pContext) { + if (!pContext) + return false; + + if (!pContext->ContainsXFAForm()) + return true; + + CXFA_FFDocView* pXFADocView = pContext->GetXFADocView(); + if (!pXFADocView) + return false; + + CXFA_FFWidgetHandler* pWidgetHandler = pXFADocView->GetWidgetHandler(); + auto it = pXFADocView->CreateReadyNodeIterator(); + while (CXFA_Node* pNode = it->MoveToNext()) { + CXFA_EventParam preParam; + preParam.m_eType = XFA_EVENT_PostSave; + pWidgetHandler->ProcessEvent(pNode, &preParam); + } + pXFADocView->UpdateDocView(); + pContext->ClearChangeMark(); + return true; +} + +bool SendPreSaveToXFADoc(CPDFXFA_Context* pContext, + std::vector>* fileList) { + if (!pContext->ContainsXFAForm()) + return true; + + CXFA_FFDocView* pXFADocView = pContext->GetXFADocView(); + if (!pXFADocView) + return true; + + CXFA_FFWidgetHandler* pWidgetHandler = pXFADocView->GetWidgetHandler(); + auto it = pXFADocView->CreateReadyNodeIterator(); + while (CXFA_Node* pNode = it->MoveToNext()) { + CXFA_EventParam preParam; + preParam.m_eType = XFA_EVENT_PreSave; + pWidgetHandler->ProcessEvent(pNode, &preParam); + } + pXFADocView->UpdateDocView(); + return SaveXFADocumentData(pContext, fileList); +} +#endif // PDF_ENABLE_XFA + +bool FPDF_Doc_Save(FPDF_DOCUMENT document, + FPDF_FILEWRITE* pFileWrite, + FPDF_DWORD flags, + FPDF_BOOL bSetVersion, + int fileVerion) { + CPDF_Document* pPDFDoc = CPDFDocumentFromFPDFDocument(document); + if (!pPDFDoc) + return 0; + +#ifdef PDF_ENABLE_XFA + CPDFXFA_Context* pContext = static_cast(document); + std::vector> fileList; + SendPreSaveToXFADoc(pContext, &fileList); +#endif // PDF_ENABLE_XFA + + if (flags < FPDF_INCREMENTAL || flags > FPDF_REMOVE_SECURITY) + flags = 0; + + CPDF_Creator fileMaker( + pPDFDoc, pdfium::MakeRetain(pFileWrite)); + if (bSetVersion) + fileMaker.SetFileVersion(fileVerion); + if (flags == FPDF_REMOVE_SECURITY) { + flags = 0; + fileMaker.RemoveSecurity(); + } + + bool bRet = fileMaker.Create(flags); +#ifdef PDF_ENABLE_XFA + SendPostSaveToXFADoc(pContext); +#endif // PDF_ENABLE_XFA + return bRet; +} + +} // namespace + +FPDF_EXPORT FPDF_BOOL FPDF_CALLCONV FPDF_SaveAsCopy(FPDF_DOCUMENT document, + FPDF_FILEWRITE* pFileWrite, + FPDF_DWORD flags) { + return FPDF_Doc_Save(document, pFileWrite, flags, false, 0); +} + +FPDF_EXPORT FPDF_BOOL FPDF_CALLCONV +FPDF_SaveWithVersion(FPDF_DOCUMENT document, + FPDF_FILEWRITE* pFileWrite, + FPDF_DWORD flags, + int fileVersion) { + return FPDF_Doc_Save(document, pFileWrite, flags, true, fileVersion); +} diff --git a/fpdfsdk/fpdf_save_embeddertest.cpp b/fpdfsdk/fpdf_save_embeddertest.cpp new file mode 100644 index 0000000000..e753ba0356 --- /dev/null +++ b/fpdfsdk/fpdf_save_embeddertest.cpp @@ -0,0 +1,101 @@ +// Copyright 2016 PDFium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include +#include + +#include "core/fxcrt/fx_string.h" +#include "public/fpdf_edit.h" +#include "public/fpdf_ppo.h" +#include "public/fpdf_save.h" +#include "public/fpdfview.h" +#include "testing/embedder_test.h" +#include "testing/gmock/include/gmock/gmock-matchers.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "testing/test_support.h" + +class FPDFSaveEmbedderTest : public EmbedderTest {}; + +TEST_F(FPDFSaveEmbedderTest, SaveSimpleDoc) { + EXPECT_TRUE(OpenDocument("hello_world.pdf")); + EXPECT_TRUE(FPDF_SaveAsCopy(document(), this, 0)); + EXPECT_THAT(GetString(), testing::StartsWith("%PDF-1.7\r\n")); + EXPECT_EQ(805u, GetString().length()); +} + +TEST_F(FPDFSaveEmbedderTest, SaveSimpleDocWithVersion) { + EXPECT_TRUE(OpenDocument("hello_world.pdf")); + EXPECT_TRUE(FPDF_SaveWithVersion(document(), this, 0, 14)); + EXPECT_THAT(GetString(), testing::StartsWith("%PDF-1.4\r\n")); + EXPECT_EQ(805u, GetString().length()); +} +TEST_F(FPDFSaveEmbedderTest, SaveSimpleDocWithBadVersion) { + EXPECT_TRUE(OpenDocument("hello_world.pdf")); + EXPECT_TRUE(FPDF_SaveWithVersion(document(), this, 0, -1)); + EXPECT_THAT(GetString(), testing::StartsWith("%PDF-1.7\r\n")); + + ClearString(); + EXPECT_TRUE(FPDF_SaveWithVersion(document(), this, 0, 0)); + EXPECT_THAT(GetString(), testing::StartsWith("%PDF-1.7\r\n")); + + ClearString(); + EXPECT_TRUE(FPDF_SaveWithVersion(document(), this, 0, 18)); + EXPECT_THAT(GetString(), testing::StartsWith("%PDF-1.7\r\n")); +} + +TEST_F(FPDFSaveEmbedderTest, SaveCopiedDoc) { + EXPECT_TRUE(OpenDocument("hello_world.pdf")); + + FPDF_PAGE page = LoadPage(0); + EXPECT_TRUE(page); + + FPDF_DOCUMENT output_doc = FPDF_CreateNewDocument(); + EXPECT_TRUE(output_doc); + EXPECT_TRUE(FPDF_ImportPages(output_doc, document(), "1", 0)); + EXPECT_TRUE(FPDF_SaveAsCopy(output_doc, this, 0)); + FPDF_CloseDocument(output_doc); + + UnloadPage(page); +} + +TEST_F(FPDFSaveEmbedderTest, SaveLinearizedDoc) { + const int kPageCount = 3; + std::string original_md5[kPageCount]; + + EXPECT_TRUE(OpenDocument("linearized.pdf")); + for (int i = 0; i < kPageCount; ++i) { + FPDF_PAGE page = LoadPage(i); + ASSERT_TRUE(page); + std::unique_ptr bitmap = RenderLoadedPage(page); + EXPECT_EQ(612, FPDFBitmap_GetWidth(bitmap.get())); + EXPECT_EQ(792, FPDFBitmap_GetHeight(bitmap.get())); + original_md5[i] = HashBitmap(bitmap.get()); + UnloadPage(page); + } + + EXPECT_TRUE(FPDF_SaveAsCopy(document(), this, 0)); + EXPECT_THAT(GetString(), testing::StartsWith("%PDF-1.6\r\n")); + EXPECT_THAT(GetString(), testing::HasSubstr("/Root ")); + EXPECT_THAT(GetString(), testing::HasSubstr("/Info ")); + EXPECT_EQ(8219u, GetString().length()); + + // Make sure new document renders the same as the old one. + EXPECT_TRUE(OpenSavedDocument()); + for (int i = 0; i < kPageCount; ++i) { + FPDF_PAGE page = LoadSavedPage(i); + ASSERT_TRUE(page); + std::unique_ptr bitmap = RenderSavedPage(page); + EXPECT_EQ(original_md5[i], HashBitmap(bitmap.get())); + CloseSavedPage(page); + } + CloseSavedDocument(); +} + +TEST_F(FPDFSaveEmbedderTest, BUG_342) { + EXPECT_TRUE(OpenDocument("hello_world.pdf")); + EXPECT_TRUE(FPDF_SaveAsCopy(document(), this, 0)); + EXPECT_THAT(GetString(), testing::HasSubstr("0000000000 65535 f\r\n")); + EXPECT_THAT(GetString(), + testing::Not(testing::HasSubstr("0000000000 65536 f\r\n"))); +} diff --git a/fpdfsdk/fpdf_text.cpp b/fpdfsdk/fpdf_text.cpp new file mode 100644 index 0000000000..7778696931 --- /dev/null +++ b/fpdfsdk/fpdf_text.cpp @@ -0,0 +1,393 @@ +// Copyright 2014 PDFium Authors. All rights reserved. +// 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_text.h" + +#include +#include + +#include "core/fpdfapi/page/cpdf_page.h" +#include "core/fpdfdoc/cpdf_viewerpreferences.h" +#include "core/fpdftext/cpdf_linkextract.h" +#include "core/fpdftext/cpdf_textpage.h" +#include "core/fpdftext/cpdf_textpagefind.h" +#include "fpdfsdk/cpdfsdk_helpers.h" +#include "third_party/base/numerics/safe_conversions.h" +#include "third_party/base/stl_util.h" + +#ifdef PDF_ENABLE_XFA +#include "fpdfsdk/fpdfxfa/cpdfxfa_context.h" +#include "fpdfsdk/fpdfxfa/cpdfxfa_page.h" +#endif // PDF_ENABLE_XFA + +#ifdef _WIN32 +#include +#endif + +namespace { + +constexpr size_t kBytesPerCharacter = sizeof(unsigned short); + +CPDF_TextPage* CPDFTextPageFromFPDFTextPage(FPDF_TEXTPAGE text_page) { + return static_cast(text_page); +} + +CPDF_TextPageFind* CPDFTextPageFindFromFPDFSchHandle(FPDF_SCHHANDLE handle) { + return static_cast(handle); +} + +CPDF_LinkExtract* CPDFLinkExtractFromFPDFPageLink(FPDF_PAGELINK link) { + return static_cast(link); +} + +} // namespace + +FPDF_EXPORT FPDF_TEXTPAGE FPDF_CALLCONV FPDFText_LoadPage(FPDF_PAGE page) { + CPDF_Page* pPDFPage = CPDFPageFromFPDFPage(page); + if (!pPDFPage) + return nullptr; + +#ifdef PDF_ENABLE_XFA + CPDFXFA_Page* pPage = (CPDFXFA_Page*)page; + CPDFXFA_Context* pContext = pPage->GetContext(); + CPDF_ViewerPreferences viewRef(pContext->GetPDFDoc()); +#else // PDF_ENABLE_XFA + CPDF_ViewerPreferences viewRef(pPDFPage->m_pDocument.Get()); +#endif // PDF_ENABLE_XFA + + CPDF_TextPage* textpage = new CPDF_TextPage( + pPDFPage, viewRef.IsDirectionR2L() ? FPDFText_Direction::Right + : FPDFText_Direction::Left); + textpage->ParseTextPage(); + return textpage; +} + +FPDF_EXPORT void FPDF_CALLCONV FPDFText_ClosePage(FPDF_TEXTPAGE text_page) { + delete CPDFTextPageFromFPDFTextPage(text_page); +} + +FPDF_EXPORT int FPDF_CALLCONV FPDFText_CountChars(FPDF_TEXTPAGE text_page) { + if (!text_page) + return -1; + + CPDF_TextPage* textpage = CPDFTextPageFromFPDFTextPage(text_page); + return textpage->CountChars(); +} + +FPDF_EXPORT unsigned int FPDF_CALLCONV +FPDFText_GetUnicode(FPDF_TEXTPAGE text_page, int index) { + if (!text_page) + return 0; + + CPDF_TextPage* textpage = CPDFTextPageFromFPDFTextPage(text_page); + if (index < 0 || index >= textpage->CountChars()) + return 0; + + FPDF_CHAR_INFO charinfo; + textpage->GetCharInfo(index, &charinfo); + return charinfo.m_Unicode; +} + +FPDF_EXPORT double FPDF_CALLCONV FPDFText_GetFontSize(FPDF_TEXTPAGE text_page, + int index) { + if (!text_page) + return 0; + CPDF_TextPage* textpage = CPDFTextPageFromFPDFTextPage(text_page); + + if (index < 0 || index >= textpage->CountChars()) + return 0; + + FPDF_CHAR_INFO charinfo; + textpage->GetCharInfo(index, &charinfo); + return charinfo.m_FontSize; +} + +FPDF_EXPORT FPDF_BOOL FPDF_CALLCONV FPDFText_GetCharBox(FPDF_TEXTPAGE text_page, + int index, + double* left, + double* right, + double* bottom, + double* top) { + if (!text_page || index < 0) + return false; + + CPDF_TextPage* textpage = CPDFTextPageFromFPDFTextPage(text_page); + if (index >= textpage->CountChars()) + return false; + + FPDF_CHAR_INFO charinfo; + textpage->GetCharInfo(index, &charinfo); + *left = charinfo.m_CharBox.left; + *right = charinfo.m_CharBox.right; + *bottom = charinfo.m_CharBox.bottom; + *top = charinfo.m_CharBox.top; + return true; +} + +FPDF_EXPORT FPDF_BOOL FPDF_CALLCONV +FPDFText_GetCharOrigin(FPDF_TEXTPAGE text_page, + int index, + double* x, + double* y) { + if (!text_page) + return false; + CPDF_TextPage* textpage = CPDFTextPageFromFPDFTextPage(text_page); + + if (index < 0 || index >= textpage->CountChars()) + return false; + FPDF_CHAR_INFO charinfo; + textpage->GetCharInfo(index, &charinfo); + *x = charinfo.m_Origin.x; + *y = charinfo.m_Origin.y; + return true; +} + +// select +FPDF_EXPORT int FPDF_CALLCONV +FPDFText_GetCharIndexAtPos(FPDF_TEXTPAGE text_page, + double x, + double y, + double xTolerance, + double yTolerance) { + if (!text_page) + return -3; + + CPDF_TextPage* textpage = CPDFTextPageFromFPDFTextPage(text_page); + return textpage->GetIndexAtPos( + CFX_PointF(static_cast(x), static_cast(y)), + CFX_SizeF(static_cast(xTolerance), + static_cast(yTolerance))); +} + +FPDF_EXPORT int FPDF_CALLCONV FPDFText_GetText(FPDF_TEXTPAGE page, + int char_start, + int char_count, + unsigned short* result) { + if (!page || char_start < 0 || char_count < 0 || !result) + return 0; + + CPDF_TextPage* textpage = CPDFTextPageFromFPDFTextPage(page); + int char_available = textpage->CountChars() - char_start; + if (char_available <= 0) + return 0; + + char_count = std::min(char_count, char_available); + if (char_count == 0) { + // Writing out "", which has a character count of 1 due to the NUL. + *result = '\0'; + return 1; + } + + WideString str = textpage->GetPageText(char_start, char_count); + + if (str.GetLength() > static_cast(char_count)) + str = str.Left(static_cast(char_count)); + + // UFT16LE_Encode doesn't handle surrogate pairs properly, so it is expected + // the number of items to stay the same. + ByteString byte_str = str.UTF16LE_Encode(); + size_t byte_str_len = byte_str.GetLength(); + int ret_count = byte_str_len / kBytesPerCharacter; + + ASSERT(ret_count <= char_count + 1); // +1 to account for the NUL terminator. + memcpy(result, byte_str.GetBuffer(byte_str_len), byte_str_len); + return ret_count; +} + +FPDF_EXPORT int FPDF_CALLCONV FPDFText_CountRects(FPDF_TEXTPAGE text_page, + int start, + int count) { + if (!text_page) + return 0; + + CPDF_TextPage* textpage = CPDFTextPageFromFPDFTextPage(text_page); + return textpage->CountRects(start, count); +} + +FPDF_EXPORT FPDF_BOOL FPDF_CALLCONV FPDFText_GetRect(FPDF_TEXTPAGE text_page, + int rect_index, + double* left, + double* top, + double* right, + double* bottom) { + if (!text_page) + return false; + + CPDF_TextPage* textpage = CPDFTextPageFromFPDFTextPage(text_page); + CFX_FloatRect rect; + bool result = textpage->GetRect(rect_index, &rect); + + *left = rect.left; + *top = rect.top; + *right = rect.right; + *bottom = rect.bottom; + return result; +} + +FPDF_EXPORT int FPDF_CALLCONV FPDFText_GetBoundedText(FPDF_TEXTPAGE text_page, + double left, + double top, + double right, + double bottom, + unsigned short* buffer, + int buflen) { + if (!text_page) + return 0; + + CPDF_TextPage* textpage = CPDFTextPageFromFPDFTextPage(text_page); + CFX_FloatRect rect((float)left, (float)bottom, (float)right, (float)top); + WideString str = textpage->GetTextByRect(rect); + + if (buflen <= 0 || !buffer) + return str.GetLength(); + + ByteString cbUTF16Str = str.UTF16LE_Encode(); + int len = cbUTF16Str.GetLength() / sizeof(unsigned short); + int size = buflen > len ? len : buflen; + memcpy(buffer, cbUTF16Str.GetBuffer(size * sizeof(unsigned short)), + size * sizeof(unsigned short)); + cbUTF16Str.ReleaseBuffer(size * sizeof(unsigned short)); + + return size; +} + +// Search +// -1 for end +FPDF_EXPORT FPDF_SCHHANDLE FPDF_CALLCONV +FPDFText_FindStart(FPDF_TEXTPAGE text_page, + FPDF_WIDESTRING findwhat, + unsigned long flags, + int start_index) { + if (!text_page) + return nullptr; + + CPDF_TextPageFind* textpageFind = + new CPDF_TextPageFind(CPDFTextPageFromFPDFTextPage(text_page)); + size_t len = WideString::WStringLength(findwhat); + textpageFind->FindFirst( + WideString::FromUTF16LE(findwhat, len), flags, + start_index >= 0 ? Optional(start_index) : Optional()); + return textpageFind; +} + +FPDF_EXPORT FPDF_BOOL FPDF_CALLCONV FPDFText_FindNext(FPDF_SCHHANDLE handle) { + if (!handle) + return false; + + CPDF_TextPageFind* textpageFind = CPDFTextPageFindFromFPDFSchHandle(handle); + return textpageFind->FindNext(); +} + +FPDF_EXPORT FPDF_BOOL FPDF_CALLCONV FPDFText_FindPrev(FPDF_SCHHANDLE handle) { + if (!handle) + return false; + + CPDF_TextPageFind* textpageFind = CPDFTextPageFindFromFPDFSchHandle(handle); + return textpageFind->FindPrev(); +} + +FPDF_EXPORT int FPDF_CALLCONV +FPDFText_GetSchResultIndex(FPDF_SCHHANDLE handle) { + if (!handle) + return 0; + + CPDF_TextPageFind* textpageFind = CPDFTextPageFindFromFPDFSchHandle(handle); + return textpageFind->GetCurOrder(); +} + +FPDF_EXPORT int FPDF_CALLCONV FPDFText_GetSchCount(FPDF_SCHHANDLE handle) { + if (!handle) + return 0; + + CPDF_TextPageFind* textpageFind = CPDFTextPageFindFromFPDFSchHandle(handle); + return textpageFind->GetMatchedCount(); +} + +FPDF_EXPORT void FPDF_CALLCONV FPDFText_FindClose(FPDF_SCHHANDLE handle) { + if (!handle) + return; + + CPDF_TextPageFind* textpageFind = CPDFTextPageFindFromFPDFSchHandle(handle); + delete textpageFind; + handle = nullptr; +} + +// web link +FPDF_EXPORT FPDF_PAGELINK FPDF_CALLCONV +FPDFLink_LoadWebLinks(FPDF_TEXTPAGE text_page) { + if (!text_page) + return nullptr; + + CPDF_LinkExtract* pageLink = + new CPDF_LinkExtract(CPDFTextPageFromFPDFTextPage(text_page)); + pageLink->ExtractLinks(); + return pageLink; +} + +FPDF_EXPORT int FPDF_CALLCONV FPDFLink_CountWebLinks(FPDF_PAGELINK link_page) { + if (!link_page) + return 0; + + CPDF_LinkExtract* pageLink = CPDFLinkExtractFromFPDFPageLink(link_page); + return pdfium::base::checked_cast(pageLink->CountLinks()); +} + +FPDF_EXPORT int FPDF_CALLCONV FPDFLink_GetURL(FPDF_PAGELINK link_page, + int link_index, + unsigned short* buffer, + int buflen) { + WideString wsUrl(L""); + if (link_page && link_index >= 0) { + CPDF_LinkExtract* pageLink = CPDFLinkExtractFromFPDFPageLink(link_page); + wsUrl = pageLink->GetURL(link_index); + } + ByteString cbUTF16URL = wsUrl.UTF16LE_Encode(); + int required = cbUTF16URL.GetLength() / sizeof(unsigned short); + if (!buffer || buflen <= 0) + return required; + + int size = std::min(required, buflen); + if (size > 0) { + int buf_size = size * sizeof(unsigned short); + memcpy(buffer, cbUTF16URL.GetBuffer(buf_size), buf_size); + } + return size; +} + +FPDF_EXPORT int FPDF_CALLCONV FPDFLink_CountRects(FPDF_PAGELINK link_page, + int link_index) { + if (!link_page || link_index < 0) + return 0; + + CPDF_LinkExtract* pageLink = CPDFLinkExtractFromFPDFPageLink(link_page); + return pdfium::CollectionSize(pageLink->GetRects(link_index)); +} + +FPDF_EXPORT FPDF_BOOL FPDF_CALLCONV FPDFLink_GetRect(FPDF_PAGELINK link_page, + int link_index, + int rect_index, + double* left, + double* top, + double* right, + double* bottom) { + if (!link_page || link_index < 0 || rect_index < 0) + return false; + + CPDF_LinkExtract* pageLink = CPDFLinkExtractFromFPDFPageLink(link_page); + std::vector rectArray = pageLink->GetRects(link_index); + if (rect_index >= pdfium::CollectionSize(rectArray)) + return false; + + *left = rectArray[rect_index].left; + *right = rectArray[rect_index].right; + *top = rectArray[rect_index].top; + *bottom = rectArray[rect_index].bottom; + return true; +} + +FPDF_EXPORT void FPDF_CALLCONV FPDFLink_CloseWebLinks(FPDF_PAGELINK link_page) { + delete CPDFLinkExtractFromFPDFPageLink(link_page); +} diff --git a/fpdfsdk/fpdf_text_embeddertest.cpp b/fpdfsdk/fpdf_text_embeddertest.cpp new file mode 100644 index 0000000000..9d09381d7c --- /dev/null +++ b/fpdfsdk/fpdf_text_embeddertest.cpp @@ -0,0 +1,756 @@ +// Copyright 2015 PDFium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include + +#include "core/fxcrt/fx_memory.h" +#include "public/fpdf_text.h" +#include "public/fpdfview.h" +#include "testing/embedder_test.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "testing/test_support.h" + +namespace { + +bool check_unsigned_shorts(const char* expected, + const unsigned short* actual, + size_t length) { + if (length > strlen(expected) + 1) + return false; + + for (size_t i = 0; i < length; ++i) { + if (actual[i] != static_cast(expected[i])) + return false; + } + return true; +} + +} // namespace + +class FPDFTextEmbeddertest : public EmbedderTest {}; + +TEST_F(FPDFTextEmbeddertest, Text) { + ASSERT_TRUE(OpenDocument("hello_world.pdf")); + FPDF_PAGE page = LoadPage(0); + ASSERT_TRUE(page); + + FPDF_TEXTPAGE textpage = FPDFText_LoadPage(page); + ASSERT_TRUE(textpage); + + static const char kExpected[] = "Hello, world!\r\nGoodbye, world!"; + unsigned short buffer[128]; + memset(buffer, 0xbd, sizeof(buffer)); + + // Check that edge cases are handled gracefully + EXPECT_EQ(0, FPDFText_GetText(textpage, 0, 128, nullptr)); + EXPECT_EQ(0, FPDFText_GetText(textpage, -1, 128, buffer)); + EXPECT_EQ(0, FPDFText_GetText(textpage, 0, -1, buffer)); + EXPECT_EQ(1, FPDFText_GetText(textpage, 0, 0, buffer)); + EXPECT_EQ(0, buffer[0]); + + // Keep going and check the next case. + memset(buffer, 0xbd, sizeof(buffer)); + EXPECT_EQ(2, FPDFText_GetText(textpage, 0, 1, buffer)); + EXPECT_EQ(kExpected[0], buffer[0]); + EXPECT_EQ(0, buffer[1]); + + // Check includes the terminating NUL that is provided. + int num_chars = FPDFText_GetText(textpage, 0, 128, buffer); + ASSERT_GE(num_chars, 0); + EXPECT_EQ(sizeof(kExpected), static_cast(num_chars)); + EXPECT_TRUE(check_unsigned_shorts(kExpected, buffer, sizeof(kExpected))); + + // Count does not include the terminating NUL in the string literal. + EXPECT_EQ(sizeof(kExpected) - 1, + static_cast(FPDFText_CountChars(textpage))); + for (size_t i = 0; i < sizeof(kExpected) - 1; ++i) { + EXPECT_EQ(static_cast(kExpected[i]), + FPDFText_GetUnicode(textpage, i)) + << " at " << i; + } + + // Extracting using a buffer that will be completely filled. Small buffer is + // 12 elements long, since it will need 2 locations per displayed character in + // the expected string, plus 2 more for the terminating character. + static const char kSmallExpected[] = "Hello"; + unsigned short small_buffer[12]; + memset(buffer, 0xbd, sizeof(buffer)); + EXPECT_EQ(6, FPDFText_GetText(textpage, 0, 5, small_buffer)); + EXPECT_TRUE(check_unsigned_shorts(kSmallExpected, small_buffer, + sizeof(kSmallExpected))); + + EXPECT_EQ(12.0, FPDFText_GetFontSize(textpage, 0)); + EXPECT_EQ(16.0, FPDFText_GetFontSize(textpage, 15)); + + double left = 0.0; + double right = 0.0; + double bottom = 0.0; + double top = 0.0; + EXPECT_FALSE(FPDFText_GetCharBox(nullptr, 4, &left, &right, &bottom, &top)); + EXPECT_DOUBLE_EQ(0.0, left); + EXPECT_DOUBLE_EQ(0.0, right); + EXPECT_DOUBLE_EQ(0.0, bottom); + EXPECT_DOUBLE_EQ(0.0, top); + EXPECT_FALSE(FPDFText_GetCharBox(textpage, -1, &left, &right, &bottom, &top)); + EXPECT_DOUBLE_EQ(0.0, left); + EXPECT_DOUBLE_EQ(0.0, right); + EXPECT_DOUBLE_EQ(0.0, bottom); + EXPECT_DOUBLE_EQ(0.0, top); + EXPECT_FALSE(FPDFText_GetCharBox(textpage, 55, &left, &right, &bottom, &top)); + EXPECT_DOUBLE_EQ(0.0, left); + EXPECT_DOUBLE_EQ(0.0, right); + EXPECT_DOUBLE_EQ(0.0, bottom); + EXPECT_DOUBLE_EQ(0.0, top); + + EXPECT_TRUE(FPDFText_GetCharBox(textpage, 4, &left, &right, &bottom, &top)); + EXPECT_NEAR(41.071, left, 0.001); + EXPECT_NEAR(46.243, right, 0.001); + EXPECT_NEAR(49.844, bottom, 0.001); + EXPECT_NEAR(55.520, top, 0.001); + + double x = 0.0; + double y = 0.0; + EXPECT_TRUE(FPDFText_GetCharOrigin(textpage, 4, &x, &y)); + EXPECT_NEAR(40.664, x, 0.001); + EXPECT_NEAR(50.000, y, 0.001); + + EXPECT_EQ(4, FPDFText_GetCharIndexAtPos(textpage, 42.0, 50.0, 1.0, 1.0)); + EXPECT_EQ(-1, FPDFText_GetCharIndexAtPos(textpage, 0.0, 0.0, 1.0, 1.0)); + EXPECT_EQ(-1, FPDFText_GetCharIndexAtPos(textpage, 199.0, 199.0, 1.0, 1.0)); + + // Test out of range indicies. + EXPECT_EQ(-1, + FPDFText_GetCharIndexAtPos(textpage, 42.0, 10000000.0, 1.0, 1.0)); + EXPECT_EQ(-1, FPDFText_GetCharIndexAtPos(textpage, -1.0, 50.0, 1.0, 1.0)); + + // Count does not include the terminating NUL in the string literal. + EXPECT_EQ(2, FPDFText_CountRects(textpage, 0, sizeof(kExpected) - 1)); + + left = 0.0; + right = 0.0; + bottom = 0.0; + top = 0.0; + EXPECT_TRUE(FPDFText_GetRect(textpage, 1, &left, &top, &right, &bottom)); + EXPECT_NEAR(20.847, left, 0.001); + EXPECT_NEAR(135.167, right, 0.001); + EXPECT_NEAR(96.655, bottom, 0.001); + EXPECT_NEAR(116.000, top, 0.001); + + // Test out of range indicies set outputs to (0.0, 0.0, 0.0, 0.0). + left = -1.0; + right = -1.0; + bottom = -1.0; + top = -1.0; + EXPECT_FALSE(FPDFText_GetRect(textpage, -1, &left, &top, &right, &bottom)); + EXPECT_EQ(0.0, left); + EXPECT_EQ(0.0, right); + EXPECT_EQ(0.0, bottom); + EXPECT_EQ(0.0, top); + + left = -2.0; + right = -2.0; + bottom = -2.0; + top = -2.0; + EXPECT_FALSE(FPDFText_GetRect(textpage, 2, &left, &top, &right, &bottom)); + EXPECT_EQ(0.0, left); + EXPECT_EQ(0.0, right); + EXPECT_EQ(0.0, bottom); + EXPECT_EQ(0.0, top); + + EXPECT_EQ(9, FPDFText_GetBoundedText(textpage, 41.0, 56.0, 82.0, 48.0, 0, 0)); + + // Extract starting at character 4 as above. + memset(buffer, 0xbd, sizeof(buffer)); + EXPECT_EQ( + 1, FPDFText_GetBoundedText(textpage, 41.0, 56.0, 82.0, 48.0, buffer, 1)); + EXPECT_TRUE(check_unsigned_shorts(kExpected + 4, buffer, 1)); + EXPECT_EQ(0xbdbd, buffer[1]); + + memset(buffer, 0xbd, sizeof(buffer)); + EXPECT_EQ( + 9, FPDFText_GetBoundedText(textpage, 41.0, 56.0, 82.0, 48.0, buffer, 9)); + EXPECT_TRUE(check_unsigned_shorts(kExpected + 4, buffer, 9)); + EXPECT_EQ(0xbdbd, buffer[9]); + + memset(buffer, 0xbd, sizeof(buffer)); + EXPECT_EQ(10, FPDFText_GetBoundedText(textpage, 41.0, 56.0, 82.0, 48.0, + buffer, 128)); + EXPECT_TRUE(check_unsigned_shorts(kExpected + 4, buffer, 9)); + EXPECT_EQ(0u, buffer[9]); + EXPECT_EQ(0xbdbd, buffer[10]); + + FPDFText_ClosePage(textpage); + UnloadPage(page); +} + +TEST_F(FPDFTextEmbeddertest, TextSearch) { + ASSERT_TRUE(OpenDocument("hello_world.pdf")); + FPDF_PAGE page = LoadPage(0); + ASSERT_TRUE(page); + + FPDF_TEXTPAGE textpage = FPDFText_LoadPage(page); + ASSERT_TRUE(textpage); + + std::unique_ptr nope = + GetFPDFWideString(L"nope"); + std::unique_ptr world = + GetFPDFWideString(L"world"); + std::unique_ptr world_caps = + GetFPDFWideString(L"WORLD"); + std::unique_ptr world_substr = + GetFPDFWideString(L"orld"); + + // No occurences of "nope" in test page. + FPDF_SCHHANDLE search = FPDFText_FindStart(textpage, nope.get(), 0, 0); + EXPECT_TRUE(search); + EXPECT_EQ(0, FPDFText_GetSchResultIndex(search)); + EXPECT_EQ(0, FPDFText_GetSchCount(search)); + + // Advancing finds nothing. + EXPECT_FALSE(FPDFText_FindNext(search)); + EXPECT_EQ(0, FPDFText_GetSchResultIndex(search)); + EXPECT_EQ(0, FPDFText_GetSchCount(search)); + + // Retreating finds nothing. + EXPECT_FALSE(FPDFText_FindPrev(search)); + EXPECT_EQ(0, FPDFText_GetSchResultIndex(search)); + EXPECT_EQ(0, FPDFText_GetSchCount(search)); + FPDFText_FindClose(search); + + // Two occurences of "world" in test page. + search = FPDFText_FindStart(textpage, world.get(), 0, 2); + EXPECT_TRUE(search); + + // Remains not found until advanced. + EXPECT_EQ(0, FPDFText_GetSchResultIndex(search)); + EXPECT_EQ(0, FPDFText_GetSchCount(search)); + + // First occurence of "world" in this test page. + EXPECT_TRUE(FPDFText_FindNext(search)); + EXPECT_EQ(7, FPDFText_GetSchResultIndex(search)); + EXPECT_EQ(5, FPDFText_GetSchCount(search)); + + // Last occurence of "world" in this test page. + EXPECT_TRUE(FPDFText_FindNext(search)); + EXPECT_EQ(24, FPDFText_GetSchResultIndex(search)); + EXPECT_EQ(5, FPDFText_GetSchCount(search)); + + // Found position unchanged when fails to advance. + EXPECT_FALSE(FPDFText_FindNext(search)); + EXPECT_EQ(24, FPDFText_GetSchResultIndex(search)); + EXPECT_EQ(5, FPDFText_GetSchCount(search)); + + // Back to first occurence. + EXPECT_TRUE(FPDFText_FindPrev(search)); + EXPECT_EQ(7, FPDFText_GetSchResultIndex(search)); + EXPECT_EQ(5, FPDFText_GetSchCount(search)); + + // Found position unchanged when fails to retreat. + EXPECT_FALSE(FPDFText_FindPrev(search)); + EXPECT_EQ(7, FPDFText_GetSchResultIndex(search)); + EXPECT_EQ(5, FPDFText_GetSchCount(search)); + FPDFText_FindClose(search); + + // Exact search unaffected by case sensitiity and whole word flags. + search = FPDFText_FindStart(textpage, world.get(), + FPDF_MATCHCASE | FPDF_MATCHWHOLEWORD, 0); + EXPECT_TRUE(search); + EXPECT_TRUE(FPDFText_FindNext(search)); + EXPECT_EQ(7, FPDFText_GetSchResultIndex(search)); + EXPECT_EQ(5, FPDFText_GetSchCount(search)); + FPDFText_FindClose(search); + + // Default is case-insensitive, so matching agaist caps works. + search = FPDFText_FindStart(textpage, world_caps.get(), 0, 0); + EXPECT_TRUE(search); + EXPECT_TRUE(FPDFText_FindNext(search)); + EXPECT_EQ(7, FPDFText_GetSchResultIndex(search)); + EXPECT_EQ(5, FPDFText_GetSchCount(search)); + FPDFText_FindClose(search); + + // But can be made case sensitive, in which case this fails. + search = FPDFText_FindStart(textpage, world_caps.get(), FPDF_MATCHCASE, 0); + EXPECT_FALSE(FPDFText_FindNext(search)); + EXPECT_EQ(0, FPDFText_GetSchResultIndex(search)); + EXPECT_EQ(0, FPDFText_GetSchCount(search)); + FPDFText_FindClose(search); + + // Default is match anywhere within word, so matching substirng works. + search = FPDFText_FindStart(textpage, world_substr.get(), 0, 0); + EXPECT_TRUE(FPDFText_FindNext(search)); + EXPECT_EQ(8, FPDFText_GetSchResultIndex(search)); + EXPECT_EQ(4, FPDFText_GetSchCount(search)); + FPDFText_FindClose(search); + + // But can be made to mach word boundaries, in which case this fails. + search = + FPDFText_FindStart(textpage, world_substr.get(), FPDF_MATCHWHOLEWORD, 0); + EXPECT_FALSE(FPDFText_FindNext(search)); + // TODO(tsepez): investigate strange index/count values in this state. + FPDFText_FindClose(search); + + FPDFText_ClosePage(textpage); + UnloadPage(page); +} + +// Test that the page has characters despite a bad stream length. +TEST_F(FPDFTextEmbeddertest, StreamLengthPastEndOfFile) { + ASSERT_TRUE(OpenDocument("bug_57.pdf")); + FPDF_PAGE page = LoadPage(0); + ASSERT_TRUE(page); + + FPDF_TEXTPAGE textpage = FPDFText_LoadPage(page); + ASSERT_TRUE(textpage); + EXPECT_EQ(13, FPDFText_CountChars(textpage)); + + FPDFText_ClosePage(textpage); + UnloadPage(page); +} + +TEST_F(FPDFTextEmbeddertest, WebLinks) { + ASSERT_TRUE(OpenDocument("weblinks.pdf")); + FPDF_PAGE page = LoadPage(0); + ASSERT_TRUE(page); + + FPDF_TEXTPAGE textpage = FPDFText_LoadPage(page); + ASSERT_TRUE(textpage); + + FPDF_PAGELINK pagelink = FPDFLink_LoadWebLinks(textpage); + EXPECT_TRUE(pagelink); + + // Page contains two HTTP-style URLs. + EXPECT_EQ(2, FPDFLink_CountWebLinks(pagelink)); + + // Only a terminating NUL required for bogus links. + EXPECT_EQ(1, FPDFLink_GetURL(pagelink, 2, nullptr, 0)); + EXPECT_EQ(1, FPDFLink_GetURL(pagelink, 1400, nullptr, 0)); + EXPECT_EQ(1, FPDFLink_GetURL(pagelink, -1, nullptr, 0)); + + // Query the number of characters required for each link (incl NUL). + EXPECT_EQ(25, FPDFLink_GetURL(pagelink, 0, nullptr, 0)); + EXPECT_EQ(26, FPDFLink_GetURL(pagelink, 1, nullptr, 0)); + + static const char expected_url[] = "http://example.com?q=foo"; + static const size_t expected_len = sizeof(expected_url); + unsigned short buffer[128]; + + // Retrieve a link with too small a buffer. Buffer will not be + // NUL-terminated, but must not be modified past indicated length, + // so pre-fill with a pattern to check write bounds. + memset(buffer, 0xbd, sizeof(buffer)); + EXPECT_EQ(1, FPDFLink_GetURL(pagelink, 0, buffer, 1)); + EXPECT_TRUE(check_unsigned_shorts(expected_url, buffer, 1)); + EXPECT_EQ(0xbdbd, buffer[1]); + + // Check buffer that doesn't have space for a terminating NUL. + memset(buffer, 0xbd, sizeof(buffer)); + EXPECT_EQ(static_cast(expected_len - 1), + FPDFLink_GetURL(pagelink, 0, buffer, expected_len - 1)); + EXPECT_TRUE(check_unsigned_shorts(expected_url, buffer, expected_len - 1)); + EXPECT_EQ(0xbdbd, buffer[expected_len - 1]); + + // Retreive link with exactly-sized buffer. + memset(buffer, 0xbd, sizeof(buffer)); + EXPECT_EQ(static_cast(expected_len), + FPDFLink_GetURL(pagelink, 0, buffer, expected_len)); + EXPECT_TRUE(check_unsigned_shorts(expected_url, buffer, expected_len)); + EXPECT_EQ(0u, buffer[expected_len - 1]); + EXPECT_EQ(0xbdbd, buffer[expected_len]); + + // Retreive link with ample-sized-buffer. + memset(buffer, 0xbd, sizeof(buffer)); + EXPECT_EQ(static_cast(expected_len), + FPDFLink_GetURL(pagelink, 0, buffer, 128)); + EXPECT_TRUE(check_unsigned_shorts(expected_url, buffer, expected_len)); + EXPECT_EQ(0u, buffer[expected_len - 1]); + EXPECT_EQ(0xbdbd, buffer[expected_len]); + + // Each link rendered in a single rect in this test page. + EXPECT_EQ(1, FPDFLink_CountRects(pagelink, 0)); + EXPECT_EQ(1, FPDFLink_CountRects(pagelink, 1)); + + // Each link rendered in a single rect in this test page. + EXPECT_EQ(0, FPDFLink_CountRects(pagelink, -1)); + EXPECT_EQ(0, FPDFLink_CountRects(pagelink, 2)); + EXPECT_EQ(0, FPDFLink_CountRects(pagelink, 10000)); + + // Check boundary of valid link index with valid rect index. + double left = 0.0; + double right = 0.0; + double top = 0.0; + double bottom = 0.0; + EXPECT_TRUE(FPDFLink_GetRect(pagelink, 0, 0, &left, &top, &right, &bottom)); + EXPECT_NEAR(50.791, left, 0.001); + EXPECT_NEAR(187.963, right, 0.001); + EXPECT_NEAR(97.624, bottom, 0.001); + EXPECT_NEAR(108.736, top, 0.001); + + // Check that valid link with invalid rect index leaves parameters unchanged. + left = -1.0; + right = -1.0; + top = -1.0; + bottom = -1.0; + EXPECT_FALSE(FPDFLink_GetRect(pagelink, 0, 1, &left, &top, &right, &bottom)); + EXPECT_EQ(-1.0, left); + EXPECT_EQ(-1.0, right); + EXPECT_EQ(-1.0, bottom); + EXPECT_EQ(-1.0, top); + + // Check that invalid link index leaves parameters unchanged. + left = -2.0; + right = -2.0; + top = -2.0; + bottom = -2.0; + EXPECT_FALSE(FPDFLink_GetRect(pagelink, -1, 0, &left, &top, &right, &bottom)); + EXPECT_EQ(-2.0, left); + EXPECT_EQ(-2.0, right); + EXPECT_EQ(-2.0, bottom); + EXPECT_EQ(-2.0, top); + + FPDFLink_CloseWebLinks(pagelink); + FPDFText_ClosePage(textpage); + UnloadPage(page); +} + +TEST_F(FPDFTextEmbeddertest, WebLinksAcrossLines) { + ASSERT_TRUE(OpenDocument("weblinks_across_lines.pdf")); + FPDF_PAGE page = LoadPage(0); + ASSERT_TRUE(page); + + FPDF_TEXTPAGE textpage = FPDFText_LoadPage(page); + ASSERT_TRUE(textpage); + + FPDF_PAGELINK pagelink = FPDFLink_LoadWebLinks(textpage); + EXPECT_TRUE(pagelink); + + static const char* const kExpectedUrls[] = { + "http://example.com", // from "http://www.example.com?\r\nfoo" + "http://example.com/", // from "http://www.example.com/\r\nfoo" + "http://example.com/test-foo", // from "http://example.com/test-\r\nfoo" + "http://abc.com/test-foo", // from "http://abc.com/test-\r\n\r\nfoo" + // Next two links from "http://www.example.com/\r\nhttp://www.abc.com/" + "http://example.com/", "http://www.abc.com", + }; + static const int kNumLinks = static_cast(FX_ArraySize(kExpectedUrls)); + + EXPECT_EQ(kNumLinks, FPDFLink_CountWebLinks(pagelink)); + + unsigned short buffer[128]; + for (int i = 0; i < kNumLinks; i++) { + const size_t expected_len = strlen(kExpectedUrls[i]) + 1; + memset(buffer, 0, FX_ArraySize(buffer)); + EXPECT_EQ(static_cast(expected_len), + FPDFLink_GetURL(pagelink, i, nullptr, 0)); + EXPECT_EQ(static_cast(expected_len), + FPDFLink_GetURL(pagelink, i, buffer, FX_ArraySize(buffer))); + EXPECT_TRUE(check_unsigned_shorts(kExpectedUrls[i], buffer, expected_len)); + } + + FPDFLink_CloseWebLinks(pagelink); + FPDFText_ClosePage(textpage); + UnloadPage(page); +} + +TEST_F(FPDFTextEmbeddertest, WebLinksAcrossLinesBug) { + ASSERT_TRUE(OpenDocument("bug_650.pdf")); + FPDF_PAGE page = LoadPage(0); + ASSERT_TRUE(page); + + FPDF_TEXTPAGE textpage = FPDFText_LoadPage(page); + ASSERT_TRUE(textpage); + + FPDF_PAGELINK pagelink = FPDFLink_LoadWebLinks(textpage); + EXPECT_TRUE(pagelink); + + EXPECT_EQ(2, FPDFLink_CountWebLinks(pagelink)); + unsigned short buffer[128] = {0}; + static const char kExpectedUrl[] = + "http://tutorial45.com/learn-autocad-basics-day-166/"; + static const int kUrlSize = static_cast(sizeof(kExpectedUrl)); + + EXPECT_EQ(kUrlSize, FPDFLink_GetURL(pagelink, 1, nullptr, 0)); + EXPECT_EQ(kUrlSize, + FPDFLink_GetURL(pagelink, 1, buffer, FX_ArraySize(buffer))); + EXPECT_TRUE(check_unsigned_shorts(kExpectedUrl, buffer, kUrlSize)); + + FPDFLink_CloseWebLinks(pagelink); + FPDFText_ClosePage(textpage); + UnloadPage(page); +} + +TEST_F(FPDFTextEmbeddertest, GetFontSize) { + ASSERT_TRUE(OpenDocument("hello_world.pdf")); + FPDF_PAGE page = LoadPage(0); + ASSERT_TRUE(page); + + FPDF_TEXTPAGE textpage = FPDFText_LoadPage(page); + ASSERT_TRUE(textpage); + + const double kExpectedFontsSizes[] = {12, 12, 12, 12, 12, 12, 12, 12, 12, 12, + 12, 12, 12, 1, 1, 16, 16, 16, 16, 16, + 16, 16, 16, 16, 16, 16, 16, 16, 16, 16}; + + int count = FPDFText_CountChars(textpage); + ASSERT_EQ(FX_ArraySize(kExpectedFontsSizes), static_cast(count)); + for (int i = 0; i < count; ++i) + EXPECT_EQ(kExpectedFontsSizes[i], FPDFText_GetFontSize(textpage, i)) << i; + + FPDFText_ClosePage(textpage); + UnloadPage(page); +} + +TEST_F(FPDFTextEmbeddertest, ToUnicode) { + ASSERT_TRUE(OpenDocument("bug_583.pdf")); + FPDF_PAGE page = LoadPage(0); + ASSERT_TRUE(page); + + FPDF_TEXTPAGE textpage = FPDFText_LoadPage(page); + ASSERT_TRUE(textpage); + + ASSERT_EQ(1, FPDFText_CountChars(textpage)); + EXPECT_EQ(static_cast(0), FPDFText_GetUnicode(textpage, 0)); + + FPDFText_ClosePage(textpage); + UnloadPage(page); +} + +TEST_F(FPDFTextEmbeddertest, Bug_921) { + ASSERT_TRUE(OpenDocument("bug_921.pdf")); + FPDF_PAGE page = LoadPage(0); + ASSERT_TRUE(page); + + FPDF_TEXTPAGE textpage = FPDFText_LoadPage(page); + ASSERT_TRUE(textpage); + + static constexpr unsigned int kData[] = { + 1095, 1077, 1083, 1086, 1074, 1077, 1095, 1077, 1089, 1082, 1086, 1077, + 32, 1089, 1090, 1088, 1072, 1076, 1072, 1085, 1080, 1077, 46, 32}; + static constexpr int kStartIndex = 238; + + ASSERT_EQ(268, FPDFText_CountChars(textpage)); + for (size_t i = 0; i < FX_ArraySize(kData); ++i) + EXPECT_EQ(kData[i], FPDFText_GetUnicode(textpage, kStartIndex + i)); + + unsigned short buffer[FX_ArraySize(kData) + 1]; + memset(buffer, 0xbd, sizeof(buffer)); + int count = + FPDFText_GetText(textpage, kStartIndex, FX_ArraySize(kData), buffer); + ASSERT_GT(count, 0); + ASSERT_EQ(FX_ArraySize(kData) + 1, static_cast(count)); + for (size_t i = 0; i < FX_ArraySize(kData); ++i) + EXPECT_EQ(kData[i], buffer[i]); + EXPECT_EQ(0, buffer[FX_ArraySize(kData)]); + + FPDFText_ClosePage(textpage); + UnloadPage(page); +} + +TEST_F(FPDFTextEmbeddertest, GetTextWithHyphen) { + ASSERT_TRUE(OpenDocument("bug_781804.pdf")); + FPDF_PAGE page = LoadPage(0); + ASSERT_TRUE(page); + + FPDF_TEXTPAGE textpage = FPDFText_LoadPage(page); + ASSERT_TRUE(textpage); + + // Check that soft hyphens are not included + // Expecting 'Veritaserum', except there is a \uFFFE where the hyphen was in + // the original text. This is a weird thing that Adobe does, which we + // replicate. + constexpr unsigned short soft_expected[] = { + 0x0056, 0x0065, 0x0072, 0x0069, 0x0074, 0x0061, 0xfffe, + 0x0073, 0x0065, 0x0072, 0x0075, 0x006D, 0x0000}; + { + constexpr int count = FX_ArraySize(soft_expected) - 1; + unsigned short buffer[FX_ArraySize(soft_expected)]; + memset(buffer, 0, sizeof(buffer)); + + EXPECT_EQ(count + 1, FPDFText_GetText(textpage, 0, count, buffer)); + for (int i = 0; i < count; i++) + EXPECT_EQ(soft_expected[i], buffer[i]); + } + + // Check that hard hyphens are included + { + // There isn't the \0 in the actual doc, but there is a \r\n, so need to + // add 1 to get aligned. + constexpr size_t offset = FX_ArraySize(soft_expected) + 1; + // Expecting 'User-\r\ngenerated', the - is a unicode character, so cannnot + // store in a char[]. + constexpr unsigned short hard_expected[] = { + 0x0055, 0x0073, 0x0065, 0x0072, 0x2010, 0x000d, 0x000a, 0x0067, 0x0065, + 0x006e, 0x0065, 0x0072, 0x0061, 0x0074, 0x0065, 0x0064, 0x0000}; + constexpr int count = FX_ArraySize(hard_expected) - 1; + unsigned short buffer[FX_ArraySize(hard_expected)]; + + EXPECT_EQ(count + 1, FPDFText_GetText(textpage, offset, count, buffer)); + for (int i = 0; i < count; i++) + EXPECT_EQ(hard_expected[i], buffer[i]); + } + + FPDFText_ClosePage(textpage); + UnloadPage(page); +} + +TEST_F(FPDFTextEmbeddertest, bug_782596) { + // If there is a regression in this test, it will only fail under ASAN + ASSERT_TRUE(OpenDocument("bug_782596.pdf")); + FPDF_PAGE page = LoadPage(0); + ASSERT_TRUE(page); + FPDF_TEXTPAGE textpage = FPDFText_LoadPage(page); + ASSERT_TRUE(textpage); + FPDFText_ClosePage(textpage); + UnloadPage(page); +} + +TEST_F(FPDFTextEmbeddertest, ControlCharacters) { + ASSERT_TRUE(OpenDocument("control_characters.pdf")); + FPDF_PAGE page = LoadPage(0); + ASSERT_TRUE(page); + + FPDF_TEXTPAGE textpage = FPDFText_LoadPage(page); + ASSERT_TRUE(textpage); + + // Should not include the control characters in the output + static const char expected[] = "Hello, world!\r\nGoodbye, world!"; + unsigned short buffer[128]; + memset(buffer, 0xbd, sizeof(buffer)); + int num_chars = FPDFText_GetText(textpage, 0, 128, buffer); + + ASSERT_GE(num_chars, 0); + EXPECT_EQ(sizeof(expected), static_cast(num_chars)); + EXPECT_TRUE(check_unsigned_shorts(expected, buffer, sizeof(expected))); + + // Attempting to get a chunk of text after the control characters + static const char expected_substring[] = "Goodbye, world!"; + // Offset is the length of 'Hello, world!\r\n' + 2 control characters in the + // original stream + static const int offset = 17; + memset(buffer, 0xbd, sizeof(buffer)); + num_chars = FPDFText_GetText(textpage, offset, 128, buffer); + + ASSERT_GE(num_chars, 0); + EXPECT_EQ(sizeof(expected_substring), static_cast(num_chars)); + EXPECT_TRUE(check_unsigned_shorts(expected_substring, buffer, + sizeof(expected_substring))); + + FPDFText_ClosePage(textpage); + UnloadPage(page); +} + +// Testing that hyphen makers (0x0002) are replacing hard hyphens when +// the word contains non-ASCII characters. +TEST_F(FPDFTextEmbeddertest, bug_1029) { + ASSERT_TRUE(OpenDocument("bug_1029.pdf")); + FPDF_PAGE page = LoadPage(0); + ASSERT_TRUE(page); + + FPDF_TEXTPAGE textpage = FPDFText_LoadPage(page); + ASSERT_TRUE(textpage); + + constexpr int page_range_offset = 171; + constexpr int page_range_length = 56; + + // This text is: + // 'METADATA table. When the split has committed, it noti' followed + // by a 'soft hyphen' (0x0002) and then 'fi'. + // + // The original text has a fi ligature, but that is broken up into + // two characters when the PDF is processed. + constexpr unsigned int expected[] = { + 0x004d, 0x0045, 0x0054, 0x0041, 0x0044, 0x0041, 0x0054, 0x0041, + 0x0020, 0x0074, 0x0061, 0x0062, 0x006c, 0x0065, 0x002e, 0x0020, + 0x0057, 0x0068, 0x0065, 0x006e, 0x0020, 0x0074, 0x0068, 0x0065, + 0x0020, 0x0073, 0x0070, 0x006c, 0x0069, 0x0074, 0x0020, 0x0068, + 0x0061, 0x0073, 0x0020, 0x0063, 0x006f, 0x006d, 0x006d, 0x0069, + 0x0074, 0x0074, 0x0065, 0x0064, 0x002c, 0x0020, 0x0069, 0x0074, + 0x0020, 0x006e, 0x006f, 0x0074, 0x0069, 0x0002, 0x0066, 0x0069}; + static_assert(page_range_length == FX_ArraySize(expected), + "Expected should be the same size as the range being " + "extracted from page."); + EXPECT_LT(page_range_offset + page_range_length, + FPDFText_CountChars(textpage)); + + for (int i = 0; i < page_range_length; ++i) { + EXPECT_EQ(expected[i], + FPDFText_GetUnicode(textpage, page_range_offset + i)); + } + + FPDFText_ClosePage(textpage); + UnloadPage(page); +} + +TEST_F(FPDFTextEmbeddertest, CountRects) { + ASSERT_TRUE(OpenDocument("hello_world.pdf")); + FPDF_PAGE page = LoadPage(0); + ASSERT_TRUE(page); + + FPDF_TEXTPAGE textpage = FPDFText_LoadPage(page); + ASSERT_TRUE(textpage); + + // Sanity check hello_world.pdf. + // |num_chars| check includes the terminating NUL that is provided. + static const char kExpected[] = "Hello, world!\r\nGoodbye, world!"; + { + unsigned short buffer[128]; + int num_chars = FPDFText_GetText(textpage, 0, 128, buffer); + ASSERT_GE(num_chars, 0); + EXPECT_EQ(sizeof(kExpected), static_cast(num_chars)); + EXPECT_TRUE(check_unsigned_shorts(kExpected, buffer, sizeof(kExpected))); + } + + // Now test FPDFText_CountRects(). + static const int kHelloWorldEnd = strlen("Hello, world!"); + static const int kGoodbyeWorldStart = kHelloWorldEnd + 2; // "\r\n" + for (int start = 0; start < kHelloWorldEnd; ++start) { + // Always grab some part of "hello world" and some part of "goodbye world" + // Since -1 means "all". + EXPECT_EQ(2, FPDFText_CountRects(textpage, start, -1)); + + // No characters always means 0 rects. + EXPECT_EQ(0, FPDFText_CountRects(textpage, start, 0)); + + // 1 character stays within "hello world" + EXPECT_EQ(1, FPDFText_CountRects(textpage, start, 1)); + + // When |start| is 0, Having |kGoodbyeWorldStart| char count does not reach + // "goodbye world". + int expected_value = start ? 2 : 1; + EXPECT_EQ(expected_value, + FPDFText_CountRects(textpage, start, kGoodbyeWorldStart)); + + // Extremely large character count will always return 2 rects because + // |start| starts inside "hello world". + EXPECT_EQ(2, FPDFText_CountRects(textpage, start, 500)); + } + + // Now test negative counts. + for (int start = 0; start < kHelloWorldEnd; ++start) { + EXPECT_EQ(2, FPDFText_CountRects(textpage, start, -100)); + EXPECT_EQ(2, FPDFText_CountRects(textpage, start, -2)); + } + + // Now test larger start values. + const int kExpectedLength = strlen(kExpected); + for (int start = kGoodbyeWorldStart + 1; start < kExpectedLength; ++start) { + EXPECT_EQ(1, FPDFText_CountRects(textpage, start, -1)); + EXPECT_EQ(0, FPDFText_CountRects(textpage, start, 0)); + EXPECT_EQ(1, FPDFText_CountRects(textpage, start, 1)); + EXPECT_EQ(1, FPDFText_CountRects(textpage, start, 2)); + EXPECT_EQ(1, FPDFText_CountRects(textpage, start, 500)); + } + + // TODO(thestig): This crashes. Fix and enable. + // Now test start values that starts beyond the end of the text. + for (int start = kExpectedLength; start < 100; ++start) { + EXPECT_EQ(0, FPDFText_CountRects(textpage, start, -1)); + EXPECT_EQ(0, FPDFText_CountRects(textpage, start, 0)); + EXPECT_EQ(0, FPDFText_CountRects(textpage, start, 1)); + EXPECT_EQ(0, FPDFText_CountRects(textpage, start, 2)); + EXPECT_EQ(0, FPDFText_CountRects(textpage, start, 500)); + } + + FPDFText_ClosePage(textpage); + UnloadPage(page); +} diff --git a/fpdfsdk/fpdf_view.cpp b/fpdfsdk/fpdf_view.cpp new file mode 100644 index 0000000000..a0877f38b5 --- /dev/null +++ b/fpdfsdk/fpdf_view.cpp @@ -0,0 +1,1159 @@ +// Copyright 2014 PDFium Authors. All rights reserved. +// 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/fpdfview.h" + +#include +#include + +#include "core/fpdfapi/cpdf_modulemgr.h" +#include "core/fpdfapi/cpdf_pagerendercontext.h" +#include "core/fpdfapi/page/cpdf_page.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_name.h" +#include "core/fpdfapi/parser/cpdf_parser.h" +#include "core/fpdfapi/parser/fpdf_parser_decode.h" +#include "core/fpdfapi/render/cpdf_progressiverenderer.h" +#include "core/fpdfapi/render/cpdf_rendercontext.h" +#include "core/fpdfapi/render/cpdf_renderoptions.h" +#include "core/fpdfdoc/cpdf_annotlist.h" +#include "core/fpdfdoc/cpdf_nametree.h" +#include "core/fpdfdoc/cpdf_occontext.h" +#include "core/fpdfdoc/cpdf_viewerpreferences.h" +#include "core/fxcrt/fx_stream.h" +#include "core/fxcrt/fx_system.h" +#include "core/fxge/cfx_defaultrenderdevice.h" +#include "core/fxge/cfx_gemodule.h" +#include "core/fxge/cfx_renderdevice.h" +#include "fpdfsdk/cpdfsdk_customaccess.h" +#include "fpdfsdk/cpdfsdk_formfillenvironment.h" +#include "fpdfsdk/cpdfsdk_helpers.h" +#include "fpdfsdk/cpdfsdk_memoryaccess.h" +#include "fpdfsdk/cpdfsdk_pageview.h" +#include "fpdfsdk/ipdfsdk_pauseadapter.h" +#include "fxjs/ijs_runtime.h" +#include "public/fpdf_formfill.h" +#include "third_party/base/ptr_util.h" + +#ifdef PDF_ENABLE_XFA +#include "fpdfsdk/fpdfxfa/cpdfxfa_context.h" +#include "fpdfsdk/fpdfxfa/cpdfxfa_page.h" +#include "fxbarcode/BC_Library.h" +#endif // PDF_ENABLE_XFA + +#if _FX_PLATFORM_ == _FX_PLATFORM_WINDOWS_ +#include "core/fxge/cfx_windowsrenderdevice.h" +#include "public/fpdf_edit.h" + +// These checks are here because core/ and public/ cannot depend on each other. +static_assert(WindowsPrintMode::kModeEmf == FPDF_PRINTMODE_EMF, + "WindowsPrintMode::kModeEmf value mismatch"); +static_assert(WindowsPrintMode::kModeTextOnly == FPDF_PRINTMODE_TEXTONLY, + "WindowsPrintMode::kModeTextOnly value mismatch"); +static_assert(WindowsPrintMode::kModePostScript2 == FPDF_PRINTMODE_POSTSCRIPT2, + "WindowsPrintMode::kModePostScript2 value mismatch"); +static_assert(WindowsPrintMode::kModePostScript3 == FPDF_PRINTMODE_POSTSCRIPT3, + "WindowsPrintMode::kModePostScript3 value mismatch"); +#endif + +namespace { + +bool g_bLibraryInitialized = false; + +void RenderPageImpl(CPDF_PageRenderContext* pContext, + CPDF_Page* pPage, + const CFX_Matrix& matrix, + const FX_RECT& clipping_rect, + int flags, + bool bNeedToRestore, + IPDFSDK_PauseAdapter* pause) { + if (!pContext->m_pOptions) + pContext->m_pOptions = pdfium::MakeUnique(); + + uint32_t option_flags = pContext->m_pOptions->GetFlags(); + if (flags & FPDF_LCD_TEXT) + option_flags |= RENDER_CLEARTYPE; + else + option_flags &= ~RENDER_CLEARTYPE; + + if (flags & FPDF_NO_NATIVETEXT) + option_flags |= RENDER_NO_NATIVETEXT; + if (flags & FPDF_RENDER_LIMITEDIMAGECACHE) + option_flags |= RENDER_LIMITEDIMAGECACHE; + if (flags & FPDF_RENDER_FORCEHALFTONE) + option_flags |= RENDER_FORCE_HALFTONE; +#ifndef PDF_ENABLE_XFA + if (flags & FPDF_RENDER_NO_SMOOTHTEXT) + option_flags |= RENDER_NOTEXTSMOOTH; + if (flags & FPDF_RENDER_NO_SMOOTHIMAGE) + option_flags |= RENDER_NOIMAGESMOOTH; + if (flags & FPDF_RENDER_NO_SMOOTHPATH) + option_flags |= RENDER_NOPATHSMOOTH; +#endif // PDF_ENABLE_XFA + pContext->m_pOptions->SetFlags(option_flags); + + // Grayscale output + if (flags & FPDF_GRAYSCALE) + pContext->m_pOptions->SetColorMode(CPDF_RenderOptions::kGray); + + const CPDF_OCContext::UsageType usage = + (flags & FPDF_PRINTING) ? CPDF_OCContext::Print : CPDF_OCContext::View; + pContext->m_pOptions->SetOCContext( + pdfium::MakeRetain(pPage->m_pDocument.Get(), usage)); + + pContext->m_pDevice->SaveState(); + pContext->m_pDevice->SetClip_Rect(clipping_rect); + pContext->m_pContext = pdfium::MakeUnique(pPage); + pContext->m_pContext->AppendLayer(pPage, &matrix); + + if (flags & FPDF_ANNOT) { + pContext->m_pAnnots = pdfium::MakeUnique(pPage); + bool bPrinting = pContext->m_pDevice->GetDeviceClass() != FXDC_DISPLAY; + pContext->m_pAnnots->DisplayAnnots(pPage, pContext->m_pContext.get(), + bPrinting, &matrix, false, nullptr); + } + + pContext->m_pRenderer = pdfium::MakeUnique( + pContext->m_pContext.get(), pContext->m_pDevice.get(), + pContext->m_pOptions.get()); + pContext->m_pRenderer->Start(pause); + if (bNeedToRestore) + pContext->m_pDevice->RestoreState(false); +} + +FPDF_DOCUMENT LoadDocumentImpl( + const RetainPtr& pFileAccess, + FPDF_BYTESTRING password) { + if (!pFileAccess) { + ProcessParseError(CPDF_Parser::FILE_ERROR); + return nullptr; + } + + auto pParser = pdfium::MakeUnique(); + pParser->SetPassword(password); + + auto pDocument = pdfium::MakeUnique(std::move(pParser)); + CPDF_Parser::Error error = + pDocument->GetParser()->StartParse(pFileAccess, pDocument.get()); + if (error != CPDF_Parser::SUCCESS) { + ProcessParseError(error); + return nullptr; + } + CheckUnSupportError(pDocument.get(), error); + return FPDFDocumentFromCPDFDocument(pDocument.release()); +} + +} // namespace + +FPDF_EXPORT void FPDF_CALLCONV FPDF_InitLibrary() { + FPDF_InitLibraryWithConfig(nullptr); +} + +FPDF_EXPORT void FPDF_CALLCONV +FPDF_InitLibraryWithConfig(const FPDF_LIBRARY_CONFIG* cfg) { + if (g_bLibraryInitialized) + return; + + FXMEM_InitializePartitionAlloc(); + + CFX_GEModule* pModule = CFX_GEModule::Get(); + pModule->Init(cfg ? cfg->m_pUserFontPaths : nullptr); + + CPDF_ModuleMgr* pModuleMgr = CPDF_ModuleMgr::Get(); + pModuleMgr->Init(); + +#ifdef PDF_ENABLE_XFA + BC_Library_Init(); +#endif // PDF_ENABLE_XFA + if (cfg && cfg->version >= 2) + IJS_Runtime::Initialize(cfg->m_v8EmbedderSlot, cfg->m_pIsolate); + + g_bLibraryInitialized = true; +} + +FPDF_EXPORT void FPDF_CALLCONV FPDF_DestroyLibrary() { + if (!g_bLibraryInitialized) + return; + +#ifdef PDF_ENABLE_XFA + BC_Library_Destroy(); +#endif // PDF_ENABLE_XFA + + CPDF_ModuleMgr::Destroy(); + CFX_GEModule::Destroy(); + + IJS_Runtime::Destroy(); + + g_bLibraryInitialized = false; +} + +FPDF_EXPORT void FPDF_CALLCONV FPDF_SetSandBoxPolicy(FPDF_DWORD policy, + FPDF_BOOL enable) { + return FSDK_SetSandBoxPolicy(policy, enable); +} + +#if defined(_WIN32) +#if defined(PDFIUM_PRINT_TEXT_WITH_GDI) +FPDF_EXPORT void FPDF_CALLCONV +FPDF_SetTypefaceAccessibleFunc(PDFiumEnsureTypefaceCharactersAccessible func) { + g_pdfium_typeface_accessible_func = func; +} + +FPDF_EXPORT void FPDF_CALLCONV FPDF_SetPrintTextWithGDI(FPDF_BOOL use_gdi) { + g_pdfium_print_text_with_gdi = !!use_gdi; +} +#endif // PDFIUM_PRINT_TEXT_WITH_GDI + +FPDF_EXPORT FPDF_BOOL FPDF_CALLCONV +FPDF_SetPrintPostscriptLevel(int postscript_level) { + return postscript_level != 1 && FPDF_SetPrintMode(postscript_level); +} + +FPDF_EXPORT FPDF_BOOL FPDF_CALLCONV FPDF_SetPrintMode(int mode) { + if (mode < FPDF_PRINTMODE_EMF || mode > FPDF_PRINTMODE_POSTSCRIPT3) + return FALSE; + g_pdfium_print_mode = mode; + return TRUE; +} +#endif // defined(_WIN32) + +FPDF_EXPORT FPDF_DOCUMENT FPDF_CALLCONV +FPDF_LoadDocument(FPDF_STRING file_path, FPDF_BYTESTRING password) { + // NOTE: the creation of the file needs to be by the embedder on the + // other side of this API. + return LoadDocumentImpl(IFX_SeekableReadStream::CreateFromFilename(file_path), + password); +} + +FPDF_EXPORT int FPDF_CALLCONV FPDF_GetFormType(FPDF_DOCUMENT document) { + const CPDF_Document* pDoc = CPDFDocumentFromFPDFDocument(document); + if (!pDoc) + return FORMTYPE_NONE; + + const CPDF_Dictionary* pRoot = pDoc->GetRoot(); + if (!pRoot) + return FORMTYPE_NONE; + + CPDF_Dictionary* pAcroForm = pRoot->GetDictFor("AcroForm"); + if (!pAcroForm) + return FORMTYPE_NONE; + + CPDF_Object* pXFA = pAcroForm->GetObjectFor("XFA"); + if (!pXFA) + return FORMTYPE_ACRO_FORM; + + bool bNeedsRendering = pRoot->GetBooleanFor("NeedsRendering", false); + return bNeedsRendering ? FORMTYPE_XFA_FULL : FORMTYPE_XFA_FOREGROUND; +} + +#ifdef PDF_ENABLE_XFA +FPDF_EXPORT FPDF_BOOL FPDF_CALLCONV FPDF_LoadXFA(FPDF_DOCUMENT document) { + return document && static_cast(document)->LoadXFADoc(); +} +#endif // PDF_ENABLE_XFA + +FPDF_EXPORT FPDF_DOCUMENT FPDF_CALLCONV +FPDF_LoadMemDocument(const void* data_buf, int size, FPDF_BYTESTRING password) { + return LoadDocumentImpl(pdfium::MakeRetain( + static_cast(data_buf), size), + password); +} + +FPDF_EXPORT FPDF_DOCUMENT FPDF_CALLCONV +FPDF_LoadCustomDocument(FPDF_FILEACCESS* pFileAccess, + FPDF_BYTESTRING password) { + return LoadDocumentImpl(pdfium::MakeRetain(pFileAccess), + password); +} + +FPDF_EXPORT FPDF_BOOL FPDF_CALLCONV FPDF_GetFileVersion(FPDF_DOCUMENT doc, + int* fileVersion) { + if (!fileVersion) + return false; + + *fileVersion = 0; + CPDF_Document* pDoc = CPDFDocumentFromFPDFDocument(doc); + if (!pDoc) + return false; + + const CPDF_Parser* pParser = pDoc->GetParser(); + if (!pParser) + return false; + + *fileVersion = pParser->GetFileVersion(); + return true; +} + +// jabdelmalek: changed return type from uint32_t to build on Linux (and match +// header). +FPDF_EXPORT unsigned long FPDF_CALLCONV +FPDF_GetDocPermissions(FPDF_DOCUMENT document) { + CPDF_Document* pDoc = CPDFDocumentFromFPDFDocument(document); + // https://bugs.chromium.org/p/pdfium/issues/detail?id=499 + if (!pDoc) { +#ifndef PDF_ENABLE_XFA + return 0; +#else // PDF_ENABLE_XFA + return 0xFFFFFFFF; +#endif // PDF_ENABLE_XFA + } + + return pDoc->GetUserPermissions(); +} + +FPDF_EXPORT int FPDF_CALLCONV +FPDF_GetSecurityHandlerRevision(FPDF_DOCUMENT document) { + CPDF_Document* pDoc = CPDFDocumentFromFPDFDocument(document); + if (!pDoc || !pDoc->GetParser()) + return -1; + + CPDF_Dictionary* pDict = pDoc->GetParser()->GetEncryptDict(); + return pDict ? pDict->GetIntegerFor("R") : -1; +} + +FPDF_EXPORT int FPDF_CALLCONV FPDF_GetPageCount(FPDF_DOCUMENT document) { + UnderlyingDocumentType* pDoc = UnderlyingFromFPDFDocument(document); + return pDoc ? pDoc->GetPageCount() : 0; +} + +FPDF_EXPORT FPDF_PAGE FPDF_CALLCONV FPDF_LoadPage(FPDF_DOCUMENT document, + int page_index) { + UnderlyingDocumentType* pDoc = UnderlyingFromFPDFDocument(document); + if (!pDoc) + return nullptr; + + if (page_index < 0 || page_index >= pDoc->GetPageCount()) + return nullptr; + +#ifdef PDF_ENABLE_XFA + return pDoc->GetXFAPage(page_index).Leak(); +#else // PDF_ENABLE_XFA + CPDF_Dictionary* pDict = pDoc->GetPage(page_index); + if (!pDict) + return nullptr; + + CPDF_Page* pPage = new CPDF_Page(pDoc, pDict, true); + pPage->ParseContent(); + return pPage; +#endif // PDF_ENABLE_XFA +} + +FPDF_EXPORT double FPDF_CALLCONV FPDF_GetPageWidth(FPDF_PAGE page) { + UnderlyingPageType* pPage = UnderlyingFromFPDFPage(page); + return pPage ? pPage->GetPageWidth() : 0.0; +} + +FPDF_EXPORT double FPDF_CALLCONV FPDF_GetPageHeight(FPDF_PAGE page) { + UnderlyingPageType* pPage = UnderlyingFromFPDFPage(page); + return pPage ? pPage->GetPageHeight() : 0.0; +} + +FPDF_EXPORT FPDF_BOOL FPDF_CALLCONV FPDF_GetPageBoundingBox(FPDF_PAGE page, + FS_RECTF* rect) { + if (!rect) + return false; + + CPDF_Page* pPage = CPDFPageFromFPDFPage(page); + if (!pPage) + return false; + + FSRECTFFromCFXFloatRect(pPage->GetPageBBox(), rect); + return true; +} + +#if defined(_WIN32) +namespace { + +const double kEpsilonSize = 0.01f; + +void GetScaling(CPDF_Page* pPage, + int size_x, + int size_y, + int rotate, + double* scale_x, + double* scale_y) { + ASSERT(pPage); + ASSERT(scale_x); + ASSERT(scale_y); + double page_width = pPage->GetPageWidth(); + double page_height = pPage->GetPageHeight(); + if (page_width < kEpsilonSize || page_height < kEpsilonSize) + return; + + if (rotate % 2 == 0) { + *scale_x = size_x / page_width; + *scale_y = size_y / page_height; + } else { + *scale_x = size_y / page_width; + *scale_y = size_x / page_height; + } +} + +FX_RECT GetMaskDimensionsAndOffsets(CPDF_Page* pPage, + int start_x, + int start_y, + int size_x, + int size_y, + int rotate, + const CFX_FloatRect& mask_box) { + double scale_x = 0.0f; + double scale_y = 0.0f; + GetScaling(pPage, size_x, size_y, rotate, &scale_x, &scale_y); + if (scale_x < kEpsilonSize || scale_y < kEpsilonSize) + return FX_RECT(); + + // Compute sizes in page points. Round down to catch the entire bitmap. + int start_x_bm = static_cast(mask_box.left * scale_x); + int start_y_bm = static_cast(mask_box.bottom * scale_y); + int size_x_bm = static_cast(mask_box.right * scale_x + 1.0f) - + static_cast(mask_box.left * scale_x); + int size_y_bm = static_cast(mask_box.top * scale_y + 1.0f) - + static_cast(mask_box.bottom * scale_y); + + // Get page rotation + int page_rotation = pPage->GetPageRotation(); + + // Compute offsets + int offset_x = 0; + int offset_y = 0; + if (size_x > size_y) + std::swap(size_x_bm, size_y_bm); + + switch ((rotate + page_rotation) % 4) { + case 0: + offset_x = start_x_bm + start_x; + offset_y = start_y + size_y - size_y_bm - start_y_bm; + break; + case 1: + offset_x = start_y_bm + start_x; + offset_y = start_x_bm + start_y; + break; + case 2: + offset_x = start_x + size_x - size_x_bm - start_x_bm; + offset_y = start_y_bm + start_y; + break; + case 3: + offset_x = start_x + size_x - size_x_bm - start_y_bm; + offset_y = start_y + size_y - size_y_bm - start_x_bm; + break; + } + return FX_RECT(offset_x, offset_y, offset_x + size_x_bm, + offset_y + size_y_bm); +} + +// Get a bitmap of just the mask section defined by |mask_box| from a full page +// bitmap |pBitmap|. +RetainPtr GetMaskBitmap(CPDF_Page* pPage, + int start_x, + int start_y, + int size_x, + int size_y, + int rotate, + const RetainPtr& pSrc, + const CFX_FloatRect& mask_box, + FX_RECT* bitmap_area) { + ASSERT(bitmap_area); + *bitmap_area = GetMaskDimensionsAndOffsets(pPage, start_x, start_y, size_x, + size_y, rotate, mask_box); + if (bitmap_area->IsEmpty()) + return nullptr; + + // Create a new bitmap to transfer part of the page bitmap to. + RetainPtr pDst = pdfium::MakeRetain(); + pDst->Create(bitmap_area->Width(), bitmap_area->Height(), FXDIB_Argb); + pDst->Clear(0x00ffffff); + pDst->TransferBitmap(0, 0, bitmap_area->Width(), bitmap_area->Height(), pSrc, + bitmap_area->left, bitmap_area->top); + return pDst; +} + +void RenderBitmap(CFX_RenderDevice* device, + const RetainPtr& pSrc, + const FX_RECT& mask_area) { + int size_x_bm = mask_area.Width(); + int size_y_bm = mask_area.Height(); + if (size_x_bm == 0 || size_y_bm == 0) + return; + + // Create a new bitmap from the old one + RetainPtr pDst = pdfium::MakeRetain(); + pDst->Create(size_x_bm, size_y_bm, FXDIB_Rgb32); + pDst->Clear(0xffffffff); + pDst->CompositeBitmap(0, 0, size_x_bm, size_y_bm, pSrc, 0, 0, + FXDIB_BLEND_NORMAL, nullptr, false); + + if (device->GetDeviceCaps(FXDC_DEVICE_CLASS) == FXDC_PRINTER) { + device->StretchDIBits(pDst, mask_area.left, mask_area.top, size_x_bm, + size_y_bm); + } else { + device->SetDIBits(pDst, mask_area.left, mask_area.top); + } +} + +} // namespace + +FPDF_EXPORT void FPDF_CALLCONV FPDF_RenderPage(HDC dc, + FPDF_PAGE page, + int start_x, + int start_y, + int size_x, + int size_y, + int rotate, + int flags) { + CPDF_Page* pPage = CPDFPageFromFPDFPage(page); + if (!pPage) + return; + pPage->SetRenderContext(pdfium::MakeUnique()); + CPDF_PageRenderContext* pContext = pPage->GetRenderContext(); + + RetainPtr pBitmap; + // Don't render the full page to bitmap for a mask unless there are a lot + // of masks. Full page bitmaps result in large spool sizes, so they should + // only be used when necessary. For large numbers of masks, rendering each + // individually is inefficient and unlikely to significantly improve spool + // size. TODO (rbpotter): Find out why this still breaks printing for some + // PDFs (see crbug.com/777837). + const bool bEnableImageMasks = false; + const bool bNewBitmap = pPage->BackgroundAlphaNeeded() || + (pPage->HasImageMask() && !bEnableImageMasks) || + pPage->GetMaskBoundingBoxes().size() > 100; + const bool bHasMask = pPage->HasImageMask() && !bNewBitmap; + if (bNewBitmap || bHasMask) { + pBitmap = pdfium::MakeRetain(); + pBitmap->Create(size_x, size_y, FXDIB_Argb); + pBitmap->Clear(0x00ffffff); + CFX_DefaultRenderDevice* pDevice = new CFX_DefaultRenderDevice; + pContext->m_pDevice = pdfium::WrapUnique(pDevice); + pDevice->Attach(pBitmap, false, nullptr, false); + if (bHasMask) { + pContext->m_pOptions = pdfium::MakeUnique(); + uint32_t option_flags = pContext->m_pOptions->GetFlags(); + option_flags |= RENDER_BREAKFORMASKS; + pContext->m_pOptions->SetFlags(option_flags); + } + } else { + pContext->m_pDevice = pdfium::MakeUnique(dc); + } + + FPDF_RenderPage_Retail(pContext, page, start_x, start_y, size_x, size_y, + rotate, flags, true, nullptr); + + if (bHasMask) { + // Finish rendering the page to bitmap and copy the correct segments + // of the page to individual image mask bitmaps. + const std::vector& mask_boxes = + pPage->GetMaskBoundingBoxes(); + std::vector bitmap_areas(mask_boxes.size()); + std::vector> bitmaps(mask_boxes.size()); + for (size_t i = 0; i < mask_boxes.size(); i++) { + bitmaps[i] = + GetMaskBitmap(pPage, start_x, start_y, size_x, size_y, rotate, + pBitmap, mask_boxes[i], &bitmap_areas[i]); + pContext->m_pRenderer->Continue(nullptr); + } + + // Reset rendering context + pPage->SetRenderContext(nullptr); + + // Begin rendering to the printer. Add flag to indicate the renderer should + // pause after each image mask. + pPage->SetRenderContext(pdfium::MakeUnique()); + pContext = pPage->GetRenderContext(); + pContext->m_pDevice = pdfium::MakeUnique(dc); + pContext->m_pOptions = pdfium::MakeUnique(); + + uint32_t option_flags = pContext->m_pOptions->GetFlags(); + option_flags |= RENDER_BREAKFORMASKS; + pContext->m_pOptions->SetFlags(option_flags); + + FPDF_RenderPage_Retail(pContext, page, start_x, start_y, size_x, size_y, + rotate, flags, true, nullptr); + + // Render masks + for (size_t i = 0; i < mask_boxes.size(); i++) { + // Render the bitmap for the mask and free the bitmap. + if (bitmaps[i]) { // will be null if mask has zero area + RenderBitmap(pContext->m_pDevice.get(), bitmaps[i], bitmap_areas[i]); + } + // Render the next portion of page. + pContext->m_pRenderer->Continue(nullptr); + } + } else if (bNewBitmap) { + CFX_WindowsRenderDevice WinDC(dc); + if (WinDC.GetDeviceCaps(FXDC_DEVICE_CLASS) == FXDC_PRINTER) { + auto pDst = pdfium::MakeRetain(); + int pitch = pBitmap->GetPitch(); + pDst->Create(size_x, size_y, FXDIB_Rgb32); + memset(pDst->GetBuffer(), -1, pitch * size_y); + pDst->CompositeBitmap(0, 0, size_x, size_y, pBitmap, 0, 0, + FXDIB_BLEND_NORMAL, nullptr, false); + WinDC.StretchDIBits(pDst, 0, 0, size_x, size_y); + } else { + WinDC.SetDIBits(pBitmap, 0, 0); + } + } + + pPage->SetRenderContext(nullptr); +} +#endif // defined(_WIN32) + +FPDF_EXPORT void FPDF_CALLCONV FPDF_RenderPageBitmap(FPDF_BITMAP bitmap, + FPDF_PAGE page, + int start_x, + int start_y, + int size_x, + int size_y, + int rotate, + int flags) { + if (!bitmap) + return; + + CPDF_Page* pPage = CPDFPageFromFPDFPage(page); + if (!pPage) + return; + + CPDF_PageRenderContext* pContext = new CPDF_PageRenderContext; + pPage->SetRenderContext(pdfium::WrapUnique(pContext)); + + CFX_DefaultRenderDevice* pDevice = new CFX_DefaultRenderDevice; + pContext->m_pDevice.reset(pDevice); + + RetainPtr pBitmap(CFXBitmapFromFPDFBitmap(bitmap)); + pDevice->Attach(pBitmap, !!(flags & FPDF_REVERSE_BYTE_ORDER), nullptr, false); + FPDF_RenderPage_Retail(pContext, page, start_x, start_y, size_x, size_y, + rotate, flags, true, nullptr); + +#ifdef _SKIA_SUPPORT_PATHS_ + pDevice->Flush(true); + pBitmap->UnPreMultiply(); +#endif + pPage->SetRenderContext(nullptr); +} + +FPDF_EXPORT void FPDF_CALLCONV +FPDF_RenderPageBitmapWithMatrix(FPDF_BITMAP bitmap, + FPDF_PAGE page, + const FS_MATRIX* matrix, + const FS_RECTF* clipping, + int flags) { + if (!bitmap) + return; + + CPDF_Page* pPage = CPDFPageFromFPDFPage(page); + if (!pPage) + return; + + CPDF_PageRenderContext* pContext = new CPDF_PageRenderContext; + pPage->SetRenderContext(pdfium::WrapUnique(pContext)); + + CFX_DefaultRenderDevice* pDevice = new CFX_DefaultRenderDevice; + pContext->m_pDevice.reset(pDevice); + + RetainPtr pBitmap(CFXBitmapFromFPDFBitmap(bitmap)); + pDevice->Attach(pBitmap, !!(flags & FPDF_REVERSE_BYTE_ORDER), nullptr, false); + + CFX_FloatRect clipping_rect; + if (clipping) + clipping_rect = CFXFloatRectFromFSRECTF(*clipping); + FX_RECT clip_rect = clipping_rect.ToFxRect(); + + CFX_Matrix transform_matrix = pPage->GetDisplayMatrix( + 0, 0, pPage->GetPageWidth(), pPage->GetPageHeight(), 0); + + if (matrix) { + transform_matrix.Concat(CFX_Matrix(matrix->a, matrix->b, matrix->c, + matrix->d, matrix->e, matrix->f)); + } + RenderPageImpl(pContext, pPage, transform_matrix, clip_rect, flags, true, + nullptr); + + pPage->SetRenderContext(nullptr); +} + +#ifdef _SKIA_SUPPORT_ +FPDF_EXPORT FPDF_RECORDER FPDF_CALLCONV FPDF_RenderPageSkp(FPDF_PAGE page, + int size_x, + int size_y) { + CPDF_Page* pPage = CPDFPageFromFPDFPage(page); + if (!pPage) + return nullptr; + + CPDF_PageRenderContext* pContext = new CPDF_PageRenderContext; + pPage->SetRenderContext(pdfium::WrapUnique(pContext)); + CFX_DefaultRenderDevice* skDevice = new CFX_DefaultRenderDevice; + FPDF_RECORDER recorder = skDevice->CreateRecorder(size_x, size_y); + pContext->m_pDevice.reset(skDevice); + FPDF_RenderPage_Retail(pContext, page, 0, 0, size_x, size_y, 0, 0, true, + nullptr); + pPage->SetRenderContext(nullptr); + return recorder; +} +#endif + +FPDF_EXPORT void FPDF_CALLCONV FPDF_ClosePage(FPDF_PAGE page) { + UnderlyingPageType* pPage = UnderlyingFromFPDFPage(page); + if (!page) + return; +#ifdef PDF_ENABLE_XFA + // Take it back across the API and throw it away. + RetainPtr().Unleak(pPage); +#else // PDF_ENABLE_XFA + CPDFSDK_PageView* pPageView = + static_cast(pPage->GetView()); + if (pPageView) { + // We're already destroying the pageview, so bail early. + if (pPageView->IsBeingDestroyed()) + return; + + if (pPageView->IsLocked()) { + pPageView->TakePageOwnership(); + return; + } + + bool owned = pPageView->OwnsPage(); + // This will delete the |pPageView| object. We must cleanup the PageView + // first because it will attempt to reset the View on the |pPage| during + // destruction. + pPageView->GetFormFillEnv()->RemovePageView(pPage); + // If the page was owned then the pageview will have deleted the page. + if (owned) + return; + } + delete pPage; +#endif // PDF_ENABLE_XFA +} + +FPDF_EXPORT void FPDF_CALLCONV FPDF_CloseDocument(FPDF_DOCUMENT document) { + delete UnderlyingFromFPDFDocument(document); +} + +FPDF_EXPORT unsigned long FPDF_CALLCONV FPDF_GetLastError() { + return GetLastError(); +} + +FPDF_EXPORT void FPDF_CALLCONV FPDF_DeviceToPage(FPDF_PAGE page, + int start_x, + int start_y, + int size_x, + int size_y, + int rotate, + int device_x, + int device_y, + double* page_x, + double* page_y) { + if (!page || !page_x || !page_y) + return; + UnderlyingPageType* pPage = UnderlyingFromFPDFPage(page); +#ifdef PDF_ENABLE_XFA + pPage->DeviceToPage(start_x, start_y, size_x, size_y, rotate, device_x, + device_y, page_x, page_y); +#else // PDF_ENABLE_XFA + CFX_Matrix page2device = + pPage->GetDisplayMatrix(start_x, start_y, size_x, size_y, rotate); + + CFX_PointF pos = page2device.GetInverse().Transform( + CFX_PointF(static_cast(device_x), static_cast(device_y))); + + *page_x = pos.x; + *page_y = pos.y; +#endif // PDF_ENABLE_XFA +} + +FPDF_EXPORT void FPDF_CALLCONV FPDF_PageToDevice(FPDF_PAGE page, + int start_x, + int start_y, + int size_x, + int size_y, + int rotate, + double page_x, + double page_y, + int* device_x, + int* device_y) { + if (!device_x || !device_y) + return; + UnderlyingPageType* pPage = UnderlyingFromFPDFPage(page); + if (!pPage) + return; +#ifdef PDF_ENABLE_XFA + pPage->PageToDevice(start_x, start_y, size_x, size_y, rotate, page_x, page_y, + device_x, device_y); +#else // PDF_ENABLE_XFA + CFX_Matrix page2device = + pPage->GetDisplayMatrix(start_x, start_y, size_x, size_y, rotate); + CFX_PointF pos = page2device.Transform( + CFX_PointF(static_cast(page_x), static_cast(page_y))); + + *device_x = FXSYS_round(pos.x); + *device_y = FXSYS_round(pos.y); +#endif // PDF_ENABLE_XFA +} + +FPDF_EXPORT FPDF_BITMAP FPDF_CALLCONV FPDFBitmap_Create(int width, + int height, + int alpha) { + auto pBitmap = pdfium::MakeRetain(); + if (!pBitmap->Create(width, height, alpha ? FXDIB_Argb : FXDIB_Rgb32)) + return nullptr; + + return pBitmap.Leak(); +} + +FPDF_EXPORT FPDF_BITMAP FPDF_CALLCONV FPDFBitmap_CreateEx(int width, + int height, + int format, + void* first_scan, + int stride) { + FXDIB_Format fx_format; + switch (format) { + case FPDFBitmap_Gray: + fx_format = FXDIB_8bppRgb; + break; + case FPDFBitmap_BGR: + fx_format = FXDIB_Rgb; + break; + case FPDFBitmap_BGRx: + fx_format = FXDIB_Rgb32; + break; + case FPDFBitmap_BGRA: + fx_format = FXDIB_Argb; + break; + default: + return nullptr; + } + auto pBitmap = pdfium::MakeRetain(); + pBitmap->Create(width, height, fx_format, static_cast(first_scan), + stride); + return pBitmap.Leak(); +} + +FPDF_EXPORT int FPDF_CALLCONV FPDFBitmap_GetFormat(FPDF_BITMAP bitmap) { + if (!bitmap) + return FPDFBitmap_Unknown; + + FXDIB_Format format = CFXBitmapFromFPDFBitmap(bitmap)->GetFormat(); + switch (format) { + case FXDIB_8bppRgb: + case FXDIB_8bppMask: + return FPDFBitmap_Gray; + case FXDIB_Rgb: + return FPDFBitmap_BGR; + case FXDIB_Rgb32: + return FPDFBitmap_BGRx; + case FXDIB_Argb: + return FPDFBitmap_BGRA; + default: + return FPDFBitmap_Unknown; + } +} + +FPDF_EXPORT void FPDF_CALLCONV FPDFBitmap_FillRect(FPDF_BITMAP bitmap, + int left, + int top, + int width, + int height, + FPDF_DWORD color) { + if (!bitmap) + return; + + CFX_DefaultRenderDevice device; + RetainPtr pBitmap(CFXBitmapFromFPDFBitmap(bitmap)); + device.Attach(pBitmap, false, nullptr, false); + if (!pBitmap->HasAlpha()) + color |= 0xFF000000; + FX_RECT rect(left, top, left + width, top + height); + device.FillRect(&rect, color); +} + +FPDF_EXPORT void* FPDF_CALLCONV FPDFBitmap_GetBuffer(FPDF_BITMAP bitmap) { + return bitmap ? CFXBitmapFromFPDFBitmap(bitmap)->GetBuffer() : nullptr; +} + +FPDF_EXPORT int FPDF_CALLCONV FPDFBitmap_GetWidth(FPDF_BITMAP bitmap) { + return bitmap ? CFXBitmapFromFPDFBitmap(bitmap)->GetWidth() : 0; +} + +FPDF_EXPORT int FPDF_CALLCONV FPDFBitmap_GetHeight(FPDF_BITMAP bitmap) { + return bitmap ? CFXBitmapFromFPDFBitmap(bitmap)->GetHeight() : 0; +} + +FPDF_EXPORT int FPDF_CALLCONV FPDFBitmap_GetStride(FPDF_BITMAP bitmap) { + return bitmap ? CFXBitmapFromFPDFBitmap(bitmap)->GetPitch() : 0; +} + +FPDF_EXPORT void FPDF_CALLCONV FPDFBitmap_Destroy(FPDF_BITMAP bitmap) { + RetainPtr destroyer; + destroyer.Unleak(CFXBitmapFromFPDFBitmap(bitmap)); +} + +void FPDF_RenderPage_Retail(CPDF_PageRenderContext* pContext, + FPDF_PAGE page, + int start_x, + int start_y, + int size_x, + int size_y, + int rotate, + int flags, + bool bNeedToRestore, + IPDFSDK_PauseAdapter* pause) { + CPDF_Page* pPage = CPDFPageFromFPDFPage(page); + if (!pPage) + return; + + RenderPageImpl( + pContext, pPage, + pPage->GetDisplayMatrix(start_x, start_y, size_x, size_y, rotate), + FX_RECT(start_x, start_y, start_x + size_x, start_y + size_y), flags, + bNeedToRestore, pause); +} + +FPDF_EXPORT int FPDF_CALLCONV FPDF_GetPageSizeByIndex(FPDF_DOCUMENT document, + int page_index, + double* width, + double* height) { + UnderlyingDocumentType* pDoc = UnderlyingFromFPDFDocument(document); + if (!pDoc) + return false; + +#ifdef PDF_ENABLE_XFA + int count = pDoc->GetPageCount(); + if (page_index < 0 || page_index >= count) + return false; + RetainPtr pPage = pDoc->GetXFAPage(page_index); + if (!pPage) + return false; + *width = pPage->GetPageWidth(); + *height = pPage->GetPageHeight(); +#else // PDF_ENABLE_XFA + CPDF_Dictionary* pDict = pDoc->GetPage(page_index); + if (!pDict) + return false; + + CPDF_Page page(pDoc, pDict, true); + *width = page.GetPageWidth(); + *height = page.GetPageHeight(); +#endif // PDF_ENABLE_XFA + + return true; +} + +FPDF_EXPORT FPDF_BOOL FPDF_CALLCONV +FPDF_VIEWERREF_GetPrintScaling(FPDF_DOCUMENT document) { + CPDF_Document* pDoc = CPDFDocumentFromFPDFDocument(document); + if (!pDoc) + return true; + CPDF_ViewerPreferences viewRef(pDoc); + return viewRef.PrintScaling(); +} + +FPDF_EXPORT int FPDF_CALLCONV +FPDF_VIEWERREF_GetNumCopies(FPDF_DOCUMENT document) { + CPDF_Document* pDoc = CPDFDocumentFromFPDFDocument(document); + if (!pDoc) + return 1; + CPDF_ViewerPreferences viewRef(pDoc); + return viewRef.NumCopies(); +} + +FPDF_EXPORT FPDF_PAGERANGE FPDF_CALLCONV +FPDF_VIEWERREF_GetPrintPageRange(FPDF_DOCUMENT document) { + CPDF_Document* pDoc = CPDFDocumentFromFPDFDocument(document); + if (!pDoc) + return nullptr; + CPDF_ViewerPreferences viewRef(pDoc); + return viewRef.PrintPageRange(); +} + +FPDF_EXPORT FPDF_DUPLEXTYPE FPDF_CALLCONV +FPDF_VIEWERREF_GetDuplex(FPDF_DOCUMENT document) { + CPDF_Document* pDoc = CPDFDocumentFromFPDFDocument(document); + if (!pDoc) + return DuplexUndefined; + CPDF_ViewerPreferences viewRef(pDoc); + ByteString duplex = viewRef.Duplex(); + if ("Simplex" == duplex) + return Simplex; + if ("DuplexFlipShortEdge" == duplex) + return DuplexFlipShortEdge; + if ("DuplexFlipLongEdge" == duplex) + return DuplexFlipLongEdge; + return DuplexUndefined; +} + +FPDF_EXPORT unsigned long FPDF_CALLCONV +FPDF_VIEWERREF_GetName(FPDF_DOCUMENT document, + FPDF_BYTESTRING key, + char* buffer, + unsigned long length) { + CPDF_Document* pDoc = CPDFDocumentFromFPDFDocument(document); + if (!pDoc) + return 0; + + CPDF_ViewerPreferences viewRef(pDoc); + ByteString bsVal; + if (!viewRef.GenericName(key, &bsVal)) + return 0; + + unsigned long dwStringLen = bsVal.GetLength() + 1; + if (buffer && length >= dwStringLen) + memcpy(buffer, bsVal.c_str(), dwStringLen); + return dwStringLen; +} + +FPDF_EXPORT FPDF_DWORD FPDF_CALLCONV +FPDF_CountNamedDests(FPDF_DOCUMENT document) { + CPDF_Document* pDoc = CPDFDocumentFromFPDFDocument(document); + if (!pDoc) + return 0; + + const CPDF_Dictionary* pRoot = pDoc->GetRoot(); + if (!pRoot) + return 0; + + CPDF_NameTree nameTree(pDoc, "Dests"); + pdfium::base::CheckedNumeric count = nameTree.GetCount(); + CPDF_Dictionary* pDest = pRoot->GetDictFor("Dests"); + if (pDest) + count += pDest->GetCount(); + + if (!count.IsValid()) + return 0; + + return count.ValueOrDie(); +} + +FPDF_EXPORT FPDF_DEST FPDF_CALLCONV +FPDF_GetNamedDestByName(FPDF_DOCUMENT document, FPDF_BYTESTRING name) { + if (!name || name[0] == 0) + return nullptr; + + CPDF_Document* pDoc = CPDFDocumentFromFPDFDocument(document); + if (!pDoc) + return nullptr; + + CPDF_NameTree name_tree(pDoc, "Dests"); + return name_tree.LookupNamedDest(pDoc, PDF_DecodeText(ByteString(name))); +} + +#ifdef PDF_ENABLE_XFA +FPDF_EXPORT FPDF_RESULT FPDF_CALLCONV FPDF_BStr_Init(FPDF_BSTR* str) { + if (!str) + return -1; + + memset(str, 0, sizeof(FPDF_BSTR)); + return 0; +} + +FPDF_EXPORT FPDF_RESULT FPDF_CALLCONV FPDF_BStr_Set(FPDF_BSTR* str, + FPDF_LPCSTR bstr, + int length) { + if (!str || !bstr || !length) + return -1; + + if (length == -1) + length = strlen(bstr); + + if (length == 0) { + FPDF_BStr_Clear(str); + return 0; + } + + if (str->str && str->len < length) + str->str = FX_Realloc(char, str->str, length + 1); + else if (!str->str) + str->str = FX_Alloc(char, length + 1); + + str->str[length] = 0; + memcpy(str->str, bstr, length); + str->len = length; + + return 0; +} + +FPDF_EXPORT FPDF_RESULT FPDF_CALLCONV FPDF_BStr_Clear(FPDF_BSTR* str) { + if (!str) + return -1; + + if (str->str) { + FX_Free(str->str); + str->str = nullptr; + } + str->len = 0; + return 0; +} +#endif // PDF_ENABLE_XFA + +FPDF_EXPORT FPDF_DEST FPDF_CALLCONV FPDF_GetNamedDest(FPDF_DOCUMENT document, + int index, + void* buffer, + long* buflen) { + if (!buffer) + *buflen = 0; + + if (index < 0) + return nullptr; + + CPDF_Document* pDoc = CPDFDocumentFromFPDFDocument(document); + if (!pDoc) + return nullptr; + + const CPDF_Dictionary* pRoot = pDoc->GetRoot(); + if (!pRoot) + return nullptr; + + CPDF_Object* pDestObj = nullptr; + WideString wsName; + CPDF_NameTree nameTree(pDoc, "Dests"); + int count = nameTree.GetCount(); + if (index >= count) { + CPDF_Dictionary* pDest = pRoot->GetDictFor("Dests"); + if (!pDest) + return nullptr; + + pdfium::base::CheckedNumeric checked_count = count; + checked_count += pDest->GetCount(); + if (!checked_count.IsValid() || index >= checked_count.ValueOrDie()) + return nullptr; + + index -= count; + int i = 0; + ByteString bsName; + for (const auto& it : *pDest) { + bsName = it.first; + pDestObj = it.second.get(); + if (!pDestObj) + continue; + if (i == index) + break; + i++; + } + wsName = PDF_DecodeText(bsName); + } else { + pDestObj = nameTree.LookupValueAndName(index, &wsName); + } + if (!pDestObj) + return nullptr; + if (CPDF_Dictionary* pDict = pDestObj->AsDictionary()) { + pDestObj = pDict->GetArrayFor("D"); + if (!pDestObj) + return nullptr; + } + if (!pDestObj->IsArray()) + return nullptr; + + ByteString utf16Name = wsName.UTF16LE_Encode(); + int len = utf16Name.GetLength(); + if (!buffer) { + *buflen = len; + } else if (len <= *buflen) { + memcpy(buffer, utf16Name.c_str(), len); + *buflen = len; + } else { + *buflen = -1; + } + return (FPDF_DEST)pDestObj; +} diff --git a/fpdfsdk/fpdf_view_c_api_test.c b/fpdfsdk/fpdf_view_c_api_test.c new file mode 100644 index 0000000000..df7ade8539 --- /dev/null +++ b/fpdfsdk/fpdf_view_c_api_test.c @@ -0,0 +1,382 @@ +// Copyright 2015 PDFium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// This "C" (not "C++") file ensures that the public headers compile +// and link for "C" (and not just "C++"). + +#include + +#include "fpdfsdk/fpdf_view_c_api_test.h" + +#include "public/fpdf_annot.h" +#include "public/fpdf_attachment.h" +#include "public/fpdf_catalog.h" +#include "public/fpdf_dataavail.h" +#include "public/fpdf_doc.h" +#include "public/fpdf_edit.h" +#include "public/fpdf_ext.h" +#include "public/fpdf_flatten.h" +#include "public/fpdf_formfill.h" +#include "public/fpdf_fwlevent.h" +#include "public/fpdf_ppo.h" +#include "public/fpdf_progressive.h" +#include "public/fpdf_save.h" +#include "public/fpdf_searchex.h" +#include "public/fpdf_structtree.h" +#include "public/fpdf_sysfontinfo.h" +#include "public/fpdf_text.h" +#include "public/fpdf_transformpage.h" +#include "public/fpdfview.h" + +// Scheme for avoiding LTO out of existence, warnings, etc. +typedef void (*fnptr)(void); // Legal generic function type for casts. +fnptr g_c_api_test_fnptr = NULL; // Extern, so can't know it doesn't change. +#define CHK(x) if ((fnptr)(x) == g_c_api_test_fnptr) return 0 + +// Function to call from gtest harness to ensure linker resolution. +int CheckPDFiumCApi() { + // fpdf_annot.h + CHK(FPDFAnnot_IsSupportedSubtype); + CHK(FPDFPage_CreateAnnot); + CHK(FPDFPage_GetAnnotCount); + CHK(FPDFPage_GetAnnot); + CHK(FPDFPage_GetAnnotIndex); + CHK(FPDFPage_CloseAnnot); + CHK(FPDFPage_RemoveAnnot); + CHK(FPDFAnnot_GetSubtype); + CHK(FPDFAnnot_IsObjectSupportedSubtype); + CHK(FPDFAnnot_UpdateObject); + CHK(FPDFAnnot_AppendObject); + CHK(FPDFAnnot_GetObjectCount); + CHK(FPDFAnnot_GetObject); + CHK(FPDFAnnot_RemoveObject); + CHK(FPDFAnnot_SetColor); + CHK(FPDFAnnot_GetColor); + CHK(FPDFAnnot_HasAttachmentPoints); + CHK(FPDFAnnot_SetAttachmentPoints); + CHK(FPDFAnnot_CountAttachmentPoints); + CHK(FPDFAnnot_GetAttachmentPoints); + CHK(FPDFAnnot_SetRect); + CHK(FPDFAnnot_GetRect); + CHK(FPDFAnnot_HasKey); + CHK(FPDFAnnot_GetValueType); + CHK(FPDFAnnot_SetStringValue); + CHK(FPDFAnnot_GetStringValue); + CHK(FPDFAnnot_SetAP); + CHK(FPDFAnnot_GetAP); + CHK(FPDFAnnot_GetLinkedAnnot); + CHK(FPDFAnnot_GetFlags); + CHK(FPDFAnnot_SetFlags); + CHK(FPDFAnnot_GetFormFieldFlags); + CHK(FPDFAnnot_GetFormFieldAtPoint); + + // fpdf_attachment.h + CHK(FPDFDoc_GetAttachmentCount); + CHK(FPDFDoc_AddAttachment); + CHK(FPDFDoc_GetAttachment); + CHK(FPDFDoc_DeleteAttachment); + CHK(FPDFAttachment_GetName); + CHK(FPDFAttachment_HasKey); + CHK(FPDFAttachment_GetValueType); + CHK(FPDFAttachment_SetStringValue); + CHK(FPDFAttachment_GetStringValue); + CHK(FPDFAttachment_SetFile); + CHK(FPDFAttachment_GetFile); + + // fpdf_catalog.h + CHK(FPDFCatalog_IsTagged); + + // fpdf_dataavail.h + CHK(FPDFAvail_Create); + CHK(FPDFAvail_Destroy); + CHK(FPDFAvail_IsDocAvail); + CHK(FPDFAvail_GetDocument); + CHK(FPDFAvail_GetFirstPageNum); + CHK(FPDFAvail_IsPageAvail); + CHK(FPDFAvail_IsFormAvail); + CHK(FPDFAvail_IsLinearized); + + // fpdf_doc.h + CHK(FPDFBookmark_GetFirstChild); + CHK(FPDFBookmark_GetNextSibling); + CHK(FPDFBookmark_GetTitle); + CHK(FPDFBookmark_Find); + CHK(FPDFBookmark_GetDest); + CHK(FPDFBookmark_GetAction); + CHK(FPDFAction_GetType); + CHK(FPDFAction_GetDest); + CHK(FPDFAction_GetFilePath); + CHK(FPDFAction_GetURIPath); + CHK(FPDFDest_GetDestPageIndex); + CHK(FPDFDest_GetPageIndex); + CHK(FPDFDest_GetLocationInPage); + CHK(FPDFDest_GetView); + CHK(FPDFLink_GetLinkAtPoint); + CHK(FPDFLink_GetLinkZOrderAtPoint); + CHK(FPDFLink_GetDest); + CHK(FPDFLink_GetAction); + CHK(FPDFLink_Enumerate); + CHK(FPDFLink_GetAnnotRect); + CHK(FPDFLink_CountQuadPoints); + CHK(FPDFLink_GetQuadPoints); + CHK(FPDF_GetMetaText); + CHK(FPDF_GetPageLabel); + + // fpdf_edit.h + CHK(FPDF_CreateNewDocument); + CHK(FPDFPage_New); + CHK(FPDFPage_Delete); + CHK(FPDFPage_GetRotation); + CHK(FPDFPage_SetRotation); + CHK(FPDFPage_InsertObject); + CHK(FPDFPage_RemoveObject); + CHK(FPDFPage_CountObject); + CHK(FPDFPage_CountObjects); + CHK(FPDFPage_GetObject); + CHK(FPDFPage_HasTransparency); + CHK(FPDFPage_GenerateContent); + CHK(FPDFPageObj_Destroy); + CHK(FPDFPageObj_HasTransparency); + CHK(FPDFPageObj_GetBounds); + CHK(FPDFPageObj_GetType); + CHK(FPDFPageObj_SetBlendMode); + CHK(FPDFPageObj_Transform); + CHK(FPDFPage_TransformAnnots); + CHK(FPDFPageObj_NewImageObj); + CHK(FPDFPageObj_CountMarks); + CHK(FPDFPageObj_GetMark); + CHK(FPDFPageObjMark_GetName); + CHK(FPDFImageObj_LoadJpegFile); + CHK(FPDFImageObj_LoadJpegFileInline); + CHK(FPDFImageObj_SetMatrix); + CHK(FPDFImageObj_SetBitmap); + CHK(FPDFImageObj_GetBitmap); + CHK(FPDFImageObj_GetImageDataDecoded); + CHK(FPDFImageObj_GetImageDataRaw); + CHK(FPDFImageObj_GetImageFilterCount); + CHK(FPDFImageObj_GetImageFilter); + CHK(FPDFImageObj_GetImageMetadata); + CHK(FPDFPageObj_CreateNewPath); + CHK(FPDFPageObj_CreateNewRect); + CHK(FPDFPath_SetStrokeColor); + CHK(FPDFPath_GetStrokeColor); + CHK(FPDFPath_SetStrokeWidth); + CHK(FPDFPath_SetFillColor); + CHK(FPDFPath_GetFillColor); + CHK(FPDFPath_CountSegments); + CHK(FPDFPath_GetPathSegment); + CHK(FPDFPathSegment_GetPoint); + CHK(FPDFPathSegment_GetType); + CHK(FPDFPathSegment_GetClose); + CHK(FPDFPath_MoveTo); + CHK(FPDFPath_LineTo); + CHK(FPDFPath_BezierTo); + CHK(FPDFPath_Close); + CHK(FPDFPath_SetDrawMode); + CHK(FPDFPath_SetLineCap); + CHK(FPDFPath_SetLineJoin); + CHK(FPDFPageObj_NewTextObj); + CHK(FPDFText_SetText); + CHK(FPDFText_SetFillColor); + CHK(FPDFText_LoadFont); + CHK(FPDFFont_Close); + CHK(FPDFPageObj_CreateTextObj); + + // fpdf_ext.h + CHK(FSDK_SetUnSpObjProcessHandler); + CHK(FPDFDoc_GetPageMode); + + // fpdf_flatten.h + CHK(FPDFPage_Flatten); + + // fpdf_fwlevent.h - no exports. + + // fpdf_formfill.h + CHK(FPDFDOC_InitFormFillEnvironment); + CHK(FPDFDOC_ExitFormFillEnvironment); + CHK(FORM_OnAfterLoadPage); + CHK(FORM_OnBeforeClosePage); + CHK(FORM_DoDocumentJSAction); + CHK(FORM_DoDocumentOpenAction); + CHK(FORM_DoDocumentAAction); + CHK(FORM_DoPageAAction); + CHK(FORM_OnMouseMove); + CHK(FORM_OnFocus); + CHK(FORM_OnLButtonDown); + CHK(FORM_OnLButtonUp); +#ifdef PDF_ENABLE_XFA + CHK(FORM_OnRButtonDown); + CHK(FORM_OnRButtonUp); +#endif + CHK(FORM_OnKeyDown); + CHK(FORM_OnKeyUp); + CHK(FORM_OnChar); + CHK(FORM_GetSelectedText); + CHK(FORM_ReplaceSelection); + CHK(FORM_ForceToKillFocus); + CHK(FPDFPage_HasFormFieldAtPoint); + CHK(FPDFPage_FormFieldZOrderAtPoint); + CHK(FPDF_SetFormFieldHighlightColor); + CHK(FPDF_SetFormFieldHighlightAlpha); + CHK(FPDF_RemoveFormFieldHighlight); + CHK(FPDF_FFLDraw); +#ifdef _SKIA_SUPPORT_ + CHK(FPDF_FFLRecord); +#endif + CHK(FPDF_GetFormType); +#ifdef PDF_ENABLE_XFA + CHK(FPDF_LoadXFA); + CHK(FPDF_Widget_Undo); + CHK(FPDF_Widget_Redo); + CHK(FPDF_Widget_SelectAll); + CHK(FPDF_Widget_Copy); + CHK(FPDF_Widget_Cut); + CHK(FPDF_Widget_Paste); + CHK(FPDF_Widget_ReplaceSpellCheckWord); + CHK(FPDF_Widget_GetSpellCheckWords); + CHK(FPDF_StringHandleCounts); + CHK(FPDF_StringHandleGetStringByIndex); + CHK(FPDF_StringHandleRelease); + CHK(FPDF_StringHandleAddString); +#endif + + // fpdf_ppo.h + CHK(FPDF_ImportPages); + CHK(FPDF_ImportNPagesToOne); + CHK(FPDF_CopyViewerPreferences); + + // fpdf_progressive.h + CHK(FPDF_RenderPageBitmap_Start); + CHK(FPDF_RenderPage_Continue); + CHK(FPDF_RenderPage_Close); + + // fpdf_save.h + CHK(FPDF_SaveAsCopy); + CHK(FPDF_SaveWithVersion); + + // fpdf_searchex.h + CHK(FPDFText_GetCharIndexFromTextIndex); + CHK(FPDFText_GetTextIndexFromCharIndex); + + // fpdf_structtree.h + CHK(FPDF_StructTree_GetForPage); + CHK(FPDF_StructTree_Close); + CHK(FPDF_StructTree_CountChildren); + CHK(FPDF_StructTree_GetChildAtIndex); + CHK(FPDF_StructElement_GetAltText); + CHK(FPDF_StructElement_GetMarkedContentID); + CHK(FPDF_StructElement_GetType); + CHK(FPDF_StructElement_GetTitle); + CHK(FPDF_StructElement_CountChildren); + CHK(FPDF_StructElement_GetChildAtIndex); + + // fpdf_sysfontinfo.h + CHK(FPDF_GetDefaultTTFMap); + CHK(FPDF_AddInstalledFont); + CHK(FPDF_SetSystemFontInfo); + CHK(FPDF_GetDefaultSystemFontInfo); + CHK(FPDF_FreeDefaultSystemFontInfo); + + // fpdf_text.h + CHK(FPDFText_LoadPage); + CHK(FPDFText_ClosePage); + CHK(FPDFText_CountChars); + CHK(FPDFText_GetUnicode); + CHK(FPDFText_GetFontSize); + CHK(FPDFText_GetCharBox); + CHK(FPDFText_GetCharOrigin); + CHK(FPDFText_GetCharIndexAtPos); + CHK(FPDFText_GetText); + CHK(FPDFText_CountRects); + CHK(FPDFText_GetRect); + CHK(FPDFText_GetBoundedText); + CHK(FPDFText_FindStart); + CHK(FPDFText_FindNext); + CHK(FPDFText_FindPrev); + CHK(FPDFText_GetSchResultIndex); + CHK(FPDFText_GetSchCount); + CHK(FPDFText_FindClose); + CHK(FPDFLink_LoadWebLinks); + CHK(FPDFLink_CountWebLinks); + CHK(FPDFLink_GetURL); + CHK(FPDFLink_CountRects); + CHK(FPDFLink_GetRect); + CHK(FPDFLink_CloseWebLinks); + + // fpdf_transformpage.h + CHK(FPDFPage_SetMediaBox); + CHK(FPDFPage_SetCropBox); + CHK(FPDFPage_GetMediaBox); + CHK(FPDFPage_GetCropBox); + CHK(FPDFPage_TransFormWithClip); + CHK(FPDFPageObj_TransformClipPath); + CHK(FPDF_CreateClipPath); + CHK(FPDF_DestroyClipPath); + CHK(FPDFPage_InsertClipPath); + + // fpdfview.h + CHK(FPDF_InitLibrary); + CHK(FPDF_InitLibraryWithConfig); + CHK(FPDF_DestroyLibrary); + CHK(FPDF_SetSandBoxPolicy); +#if defined(_WIN32) +#if defined(PDFIUM_PRINT_TEXT_WITH_GDI) + CHK(FPDF_SetTypefaceAccessibleFunc); + CHK(FPDF_SetPrintTextWithGDI); +#endif + CHK(FPDF_SetPrintPostscriptLevel); + CHK(FPDF_SetPrintMode); +#endif + CHK(FPDF_LoadDocument); + CHK(FPDF_LoadMemDocument); + CHK(FPDF_LoadCustomDocument); + CHK(FPDF_GetFileVersion); + CHK(FPDF_GetLastError); + CHK(FPDF_GetDocPermissions); + CHK(FPDF_GetSecurityHandlerRevision); + CHK(FPDF_GetPageCount); + CHK(FPDF_LoadPage); + CHK(FPDF_GetPageWidth); + CHK(FPDF_GetPageHeight); + CHK(FPDF_GetPageBoundingBox); + CHK(FPDF_GetPageSizeByIndex); +#ifdef _WIN32 + CHK(FPDF_RenderPage); +#endif + CHK(FPDF_RenderPageBitmap); + CHK(FPDF_RenderPageBitmapWithMatrix); +#ifdef _SKIA_SUPPORT_ + CHK(FPDF_RenderPageSkp); +#endif + CHK(FPDF_ClosePage); + CHK(FPDF_CloseDocument); + CHK(FPDF_DeviceToPage); + CHK(FPDF_PageToDevice); + CHK(FPDFBitmap_Create); + CHK(FPDFBitmap_CreateEx); + CHK(FPDFBitmap_GetFormat); + CHK(FPDFBitmap_FillRect); + CHK(FPDFBitmap_GetBuffer); + CHK(FPDFBitmap_GetWidth); + CHK(FPDFBitmap_GetHeight); + CHK(FPDFBitmap_GetStride); + CHK(FPDFBitmap_Destroy); + CHK(FPDF_VIEWERREF_GetPrintScaling); + CHK(FPDF_VIEWERREF_GetNumCopies); + CHK(FPDF_VIEWERREF_GetPrintPageRange); + CHK(FPDF_VIEWERREF_GetDuplex); + CHK(FPDF_VIEWERREF_GetName); + CHK(FPDF_CountNamedDests); + CHK(FPDF_GetNamedDestByName); + CHK(FPDF_GetNamedDest); +#ifdef PDF_ENABLE_XFA + CHK(FPDF_BStr_Init); + CHK(FPDF_BStr_Set); + CHK(FPDF_BStr_Clear); +#endif + + return 1; +} + +#undef CHK diff --git a/fpdfsdk/fpdf_view_c_api_test.h b/fpdfsdk/fpdf_view_c_api_test.h new file mode 100644 index 0000000000..c662f40068 --- /dev/null +++ b/fpdfsdk/fpdf_view_c_api_test.h @@ -0,0 +1,20 @@ +// Copyright 2015 PDFium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef FPDFSDK_FPDF_VIEW_C_API_TEST_H_ +#define FPDFSDK_FPDF_VIEW_C_API_TEST_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +// Function to call from gtest harness to ensure linker resolution. Returns +// 1 on success or 0 on error. +int CheckPDFiumCApi(); + +#ifdef __cplusplus +} +#endif + +#endif // FPDFSDK_FPDF_VIEW_C_API_TEST_H_ diff --git a/fpdfsdk/fpdf_view_embeddertest.cpp b/fpdfsdk/fpdf_view_embeddertest.cpp new file mode 100644 index 0000000000..0bfa1a30b2 --- /dev/null +++ b/fpdfsdk/fpdf_view_embeddertest.cpp @@ -0,0 +1,605 @@ +// Copyright 2015 PDFium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include +#include +#include +#include + +#include "fpdfsdk/fpdf_view_c_api_test.h" +#include "public/fpdfview.h" +#include "testing/embedder_test.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "testing/utils/path_service.h" + +namespace { + +class MockDownloadHints : public FX_DOWNLOADHINTS { + public: + static void SAddSegment(FX_DOWNLOADHINTS* pThis, size_t offset, size_t size) { + } + + MockDownloadHints() { + FX_DOWNLOADHINTS::version = 1; + FX_DOWNLOADHINTS::AddSegment = SAddSegment; + } + + ~MockDownloadHints() {} +}; + +} // namespace + +TEST(fpdf, CApiTest) { + EXPECT_TRUE(CheckPDFiumCApi()); +} + +class FPDFViewEmbeddertest : public EmbedderTest { + protected: + void TestRenderPageBitmapWithMatrix(FPDF_PAGE page, + const int bitmap_width, + const int bitmap_height, + const FS_MATRIX& matrix, + const FS_RECTF& rect, + const char* expected_md5); +}; + +TEST_F(FPDFViewEmbeddertest, Document) { + EXPECT_TRUE(OpenDocument("about_blank.pdf")); + EXPECT_EQ(1, GetPageCount()); + EXPECT_EQ(0, GetFirstPageNum()); + + int version; + EXPECT_TRUE(FPDF_GetFileVersion(document(), &version)); + EXPECT_EQ(14, version); + + EXPECT_EQ(0xFFFFFFFF, FPDF_GetDocPermissions(document())); + EXPECT_EQ(-1, FPDF_GetSecurityHandlerRevision(document())); +} + +TEST_F(FPDFViewEmbeddertest, LoadNonexistentDocument) { + FPDF_DOCUMENT doc = FPDF_LoadDocument("nonexistent_document.pdf", ""); + ASSERT_FALSE(doc); + EXPECT_EQ(static_cast(FPDF_GetLastError()), FPDF_ERR_FILE); +} + +// See bug 465. +TEST_F(FPDFViewEmbeddertest, EmptyDocument) { + EXPECT_TRUE(CreateEmptyDocument()); + + { + int version = 42; + EXPECT_FALSE(FPDF_GetFileVersion(document(), &version)); + EXPECT_EQ(0, version); + } + + { +#ifndef PDF_ENABLE_XFA + const unsigned long kExpected = 0; +#else + const unsigned long kExpected = static_cast(-1); +#endif + EXPECT_EQ(kExpected, FPDF_GetDocPermissions(document())); + } + + EXPECT_EQ(-1, FPDF_GetSecurityHandlerRevision(document())); + + EXPECT_EQ(0, FPDF_GetPageCount(document())); + + EXPECT_TRUE(FPDF_VIEWERREF_GetPrintScaling(document())); + EXPECT_EQ(1, FPDF_VIEWERREF_GetNumCopies(document())); + EXPECT_EQ(DuplexUndefined, FPDF_VIEWERREF_GetDuplex(document())); + + char buf[100]; + EXPECT_EQ(0U, FPDF_VIEWERREF_GetName(document(), "foo", nullptr, 0)); + EXPECT_EQ(0U, FPDF_VIEWERREF_GetName(document(), "foo", buf, sizeof(buf))); + + EXPECT_EQ(0u, FPDF_CountNamedDests(document())); +} + +TEST_F(FPDFViewEmbeddertest, LinearizedDocument) { + EXPECT_TRUE(OpenDocumentLinearized("feature_linearized_loading.pdf")); + int version; + EXPECT_TRUE(FPDF_GetFileVersion(document(), &version)); + EXPECT_EQ(16, version); +} + +TEST_F(FPDFViewEmbeddertest, Page) { + EXPECT_TRUE(OpenDocument("about_blank.pdf")); + FPDF_PAGE page = LoadPage(0); + EXPECT_NE(nullptr, page); + + EXPECT_EQ(612.0, FPDF_GetPageWidth(page)); + EXPECT_EQ(792.0, FPDF_GetPageHeight(page)); + + FS_RECTF rect; + EXPECT_TRUE(FPDF_GetPageBoundingBox(page, &rect)); + EXPECT_EQ(0.0, rect.left); + EXPECT_EQ(0.0, rect.bottom); + EXPECT_EQ(612.0, rect.right); + EXPECT_EQ(792.0, rect.top); + + UnloadPage(page); + EXPECT_EQ(nullptr, LoadPage(1)); +} + +TEST_F(FPDFViewEmbeddertest, ViewerRefDummy) { + EXPECT_TRUE(OpenDocument("about_blank.pdf")); + EXPECT_TRUE(FPDF_VIEWERREF_GetPrintScaling(document())); + EXPECT_EQ(1, FPDF_VIEWERREF_GetNumCopies(document())); + EXPECT_EQ(DuplexUndefined, FPDF_VIEWERREF_GetDuplex(document())); + + char buf[100]; + EXPECT_EQ(0U, FPDF_VIEWERREF_GetName(document(), "foo", nullptr, 0)); + EXPECT_EQ(0U, FPDF_VIEWERREF_GetName(document(), "foo", buf, sizeof(buf))); +} + +TEST_F(FPDFViewEmbeddertest, ViewerRef) { + EXPECT_TRUE(OpenDocument("viewer_ref.pdf")); + EXPECT_TRUE(FPDF_VIEWERREF_GetPrintScaling(document())); + EXPECT_EQ(5, FPDF_VIEWERREF_GetNumCopies(document())); + EXPECT_EQ(DuplexUndefined, FPDF_VIEWERREF_GetDuplex(document())); + + // Test some corner cases. + char buf[100]; + EXPECT_EQ(0U, FPDF_VIEWERREF_GetName(document(), "", buf, sizeof(buf))); + EXPECT_EQ(0U, FPDF_VIEWERREF_GetName(document(), "foo", nullptr, 0)); + EXPECT_EQ(0U, FPDF_VIEWERREF_GetName(document(), "foo", buf, sizeof(buf))); + + // Make sure |buf| does not get written into when it appears to be too small. + // NOLINTNEXTLINE(runtime/printf) + strcpy(buf, "ABCD"); + EXPECT_EQ(4U, FPDF_VIEWERREF_GetName(document(), "Foo", buf, 1)); + EXPECT_STREQ("ABCD", buf); + + // Note "Foo" is a different key from "foo". + EXPECT_EQ(4U, + FPDF_VIEWERREF_GetName(document(), "Foo", nullptr, sizeof(buf))); + ASSERT_EQ(4U, FPDF_VIEWERREF_GetName(document(), "Foo", buf, sizeof(buf))); + EXPECT_STREQ("foo", buf); + + // Try to retrieve a boolean and an integer. + EXPECT_EQ( + 0U, FPDF_VIEWERREF_GetName(document(), "HideToolbar", buf, sizeof(buf))); + EXPECT_EQ(0U, + FPDF_VIEWERREF_GetName(document(), "NumCopies", buf, sizeof(buf))); + + // Try more valid cases. + ASSERT_EQ(4U, + FPDF_VIEWERREF_GetName(document(), "Direction", buf, sizeof(buf))); + EXPECT_STREQ("R2L", buf); + ASSERT_EQ(8U, + FPDF_VIEWERREF_GetName(document(), "ViewArea", buf, sizeof(buf))); + EXPECT_STREQ("CropBox", buf); +} + +TEST_F(FPDFViewEmbeddertest, NamedDests) { + EXPECT_TRUE(OpenDocument("named_dests.pdf")); + long buffer_size; + char fixed_buffer[512]; + FPDF_DEST dest; + + // Query the size of the first item. + buffer_size = 2000000; // Absurdly large, check not used for this case. + dest = FPDF_GetNamedDest(document(), 0, nullptr, &buffer_size); + EXPECT_NE(nullptr, dest); + EXPECT_EQ(12, buffer_size); + + // Try to retrieve the first item with too small a buffer. + buffer_size = 10; + dest = FPDF_GetNamedDest(document(), 0, fixed_buffer, &buffer_size); + EXPECT_NE(nullptr, dest); + EXPECT_EQ(-1, buffer_size); + + // Try to retrieve the first item with correctly sized buffer. Item is + // taken from Dests NameTree in named_dests.pdf. + buffer_size = 12; + dest = FPDF_GetNamedDest(document(), 0, fixed_buffer, &buffer_size); + EXPECT_NE(nullptr, dest); + EXPECT_EQ(12, buffer_size); + EXPECT_EQ(std::string("F\0i\0r\0s\0t\0\0\0", 12), + std::string(fixed_buffer, buffer_size)); + + // Try to retrieve the second item with ample buffer. Item is taken + // from Dests NameTree but has a sub-dictionary in named_dests.pdf. + buffer_size = sizeof(fixed_buffer); + dest = FPDF_GetNamedDest(document(), 1, fixed_buffer, &buffer_size); + EXPECT_NE(nullptr, dest); + EXPECT_EQ(10, buffer_size); + EXPECT_EQ(std::string("N\0e\0x\0t\0\0\0", 10), + std::string(fixed_buffer, buffer_size)); + + // Try to retrieve third item with ample buffer. Item is taken + // from Dests NameTree but has a bad sub-dictionary in named_dests.pdf. + // in named_dests.pdf). + buffer_size = sizeof(fixed_buffer); + dest = FPDF_GetNamedDest(document(), 2, fixed_buffer, &buffer_size); + EXPECT_EQ(nullptr, dest); + EXPECT_EQ(sizeof(fixed_buffer), + static_cast(buffer_size)); // unmodified. + + // Try to retrieve the forth item with ample buffer. Item is taken + // from Dests NameTree but has a vale of the wrong type in named_dests.pdf. + buffer_size = sizeof(fixed_buffer); + dest = FPDF_GetNamedDest(document(), 3, fixed_buffer, &buffer_size); + EXPECT_EQ(nullptr, dest); + EXPECT_EQ(sizeof(fixed_buffer), + static_cast(buffer_size)); // unmodified. + + // Try to retrieve fifth item with ample buffer. Item taken from the + // old-style Dests dictionary object in named_dests.pdf. + buffer_size = sizeof(fixed_buffer); + dest = FPDF_GetNamedDest(document(), 4, fixed_buffer, &buffer_size); + EXPECT_NE(nullptr, dest); + EXPECT_EQ(30, buffer_size); + EXPECT_EQ(std::string("F\0i\0r\0s\0t\0A\0l\0t\0e\0r\0n\0a\0t\0e\0\0\0", 30), + std::string(fixed_buffer, buffer_size)); + + // Try to retrieve sixth item with ample buffer. Item istaken from the + // old-style Dests dictionary object but has a sub-dictionary in + // named_dests.pdf. + buffer_size = sizeof(fixed_buffer); + dest = FPDF_GetNamedDest(document(), 5, fixed_buffer, &buffer_size); + EXPECT_NE(nullptr, dest); + EXPECT_EQ(28, buffer_size); + EXPECT_EQ(std::string("L\0a\0s\0t\0A\0l\0t\0e\0r\0n\0a\0t\0e\0\0\0", 28), + std::string(fixed_buffer, buffer_size)); + + // Try to retrieve non-existent item with ample buffer. + buffer_size = sizeof(fixed_buffer); + dest = FPDF_GetNamedDest(document(), 6, fixed_buffer, &buffer_size); + EXPECT_EQ(nullptr, dest); + EXPECT_EQ(sizeof(fixed_buffer), + static_cast(buffer_size)); // unmodified. + + // Try to underflow/overflow the integer index. + buffer_size = sizeof(fixed_buffer); + dest = FPDF_GetNamedDest(document(), std::numeric_limits::max(), + fixed_buffer, &buffer_size); + EXPECT_EQ(nullptr, dest); + EXPECT_EQ(sizeof(fixed_buffer), + static_cast(buffer_size)); // unmodified. + + buffer_size = sizeof(fixed_buffer); + dest = FPDF_GetNamedDest(document(), std::numeric_limits::min(), + fixed_buffer, &buffer_size); + EXPECT_EQ(nullptr, dest); + EXPECT_EQ(sizeof(fixed_buffer), + static_cast(buffer_size)); // unmodified. + + buffer_size = sizeof(fixed_buffer); + dest = FPDF_GetNamedDest(document(), -1, fixed_buffer, &buffer_size); + EXPECT_EQ(nullptr, dest); + EXPECT_EQ(sizeof(fixed_buffer), + static_cast(buffer_size)); // unmodified. +} + +TEST_F(FPDFViewEmbeddertest, NamedDestsByName) { + EXPECT_TRUE(OpenDocument("named_dests.pdf")); + + // Null pointer returns nullptr. + FPDF_DEST dest = FPDF_GetNamedDestByName(document(), nullptr); + EXPECT_EQ(nullptr, dest); + + // Empty string returns nullptr. + dest = FPDF_GetNamedDestByName(document(), ""); + EXPECT_EQ(nullptr, dest); + + // Item from Dests NameTree. + dest = FPDF_GetNamedDestByName(document(), "First"); + EXPECT_NE(nullptr, dest); + + long ignore_len = 0; + FPDF_DEST dest_by_index = + FPDF_GetNamedDest(document(), 0, nullptr, &ignore_len); + EXPECT_EQ(dest_by_index, dest); + + // Item from Dests dictionary. + dest = FPDF_GetNamedDestByName(document(), "FirstAlternate"); + EXPECT_NE(nullptr, dest); + + ignore_len = 0; + dest_by_index = FPDF_GetNamedDest(document(), 4, nullptr, &ignore_len); + EXPECT_EQ(dest_by_index, dest); + + // Bad value type for item from Dests NameTree array. + dest = FPDF_GetNamedDestByName(document(), "WrongType"); + EXPECT_EQ(nullptr, dest); + + // No such destination in either Dest NameTree or dictionary. + dest = FPDF_GetNamedDestByName(document(), "Bogus"); + EXPECT_EQ(nullptr, dest); +} + +// The following tests pass if the document opens without crashing. +TEST_F(FPDFViewEmbeddertest, Crasher_113) { + EXPECT_TRUE(OpenDocument("bug_113.pdf")); +} + +TEST_F(FPDFViewEmbeddertest, Crasher_451830) { + // Document is damaged and can't be opened. + EXPECT_FALSE(OpenDocument("bug_451830.pdf")); +} + +TEST_F(FPDFViewEmbeddertest, Crasher_452455) { + EXPECT_TRUE(OpenDocument("bug_452455.pdf")); + FPDF_PAGE page = LoadPage(0); + EXPECT_NE(nullptr, page); + UnloadPage(page); +} + +TEST_F(FPDFViewEmbeddertest, Crasher_454695) { + // Document is damaged and can't be opened. + EXPECT_FALSE(OpenDocument("bug_454695.pdf")); +} + +TEST_F(FPDFViewEmbeddertest, Crasher_572871) { + EXPECT_TRUE(OpenDocument("bug_572871.pdf")); +} + +// It tests that document can still be loaded even the trailer has no 'Size' +// field if other information is right. +TEST_F(FPDFViewEmbeddertest, Failed_213) { + EXPECT_TRUE(OpenDocument("bug_213.pdf")); +} + +// The following tests pass if the document opens without infinite looping. +TEST_F(FPDFViewEmbeddertest, Hang_298) { + EXPECT_FALSE(OpenDocument("bug_298.pdf")); +} + +TEST_F(FPDFViewEmbeddertest, Crasher_773229) { + EXPECT_TRUE(OpenDocument("bug_773229.pdf")); +} + +// Test if the document opens without infinite looping. +// Previously this test will hang in a loop inside LoadAllCrossRefV4. After +// the fix, LoadAllCrossRefV4 will return false after detecting a cross +// reference loop. Cross references will be rebuilt successfully. +TEST_F(FPDFViewEmbeddertest, CrossRefV4Loop) { + EXPECT_TRUE(OpenDocument("bug_xrefv4_loop.pdf")); + MockDownloadHints hints; + + // Make sure calling FPDFAvail_IsDocAvail() on this file does not infinite + // loop either. See bug 875. + int ret = PDF_DATA_NOTAVAIL; + while (ret == PDF_DATA_NOTAVAIL) + ret = FPDFAvail_IsDocAvail(avail_, &hints); + EXPECT_EQ(PDF_DATA_AVAIL, ret); +} + +// The test should pass when circular references to ParseIndirectObject will not +// cause infinite loop. +TEST_F(FPDFViewEmbeddertest, Hang_343) { + EXPECT_FALSE(OpenDocument("bug_343.pdf")); +} + +// The test should pass when the absence of 'Contents' field in a signature +// dictionary will not cause an infinite loop in CPDF_SyntaxParser::GetObject(). +TEST_F(FPDFViewEmbeddertest, Hang_344) { + EXPECT_FALSE(OpenDocument("bug_344.pdf")); +} + +// The test should pass when there is no infinite recursion in +// CPDF_SyntaxParser::GetString(). +TEST_F(FPDFViewEmbeddertest, Hang_355) { + EXPECT_FALSE(OpenDocument("bug_355.pdf")); +} +// The test should pass even when the file has circular references to pages. +TEST_F(FPDFViewEmbeddertest, Hang_360) { + EXPECT_FALSE(OpenDocument("bug_360.pdf")); +} + +void FPDFViewEmbeddertest::TestRenderPageBitmapWithMatrix( + FPDF_PAGE page, + const int bitmap_width, + const int bitmap_height, + const FS_MATRIX& matrix, + const FS_RECTF& rect, + const char* expected_md5) { + FPDF_BITMAP bitmap = FPDFBitmap_Create(bitmap_width, bitmap_height, 0); + FPDFBitmap_FillRect(bitmap, 0, 0, bitmap_width, bitmap_height, 0xFFFFFFFF); + FPDF_RenderPageBitmapWithMatrix(bitmap, page, &matrix, &rect, 0); + CompareBitmap(bitmap, bitmap_width, bitmap_height, expected_md5); + FPDFBitmap_Destroy(bitmap); +} + +TEST_F(FPDFViewEmbeddertest, FPDF_RenderPageBitmapWithMatrix) { + const char kOriginalMD5[] = "0a90de37f52127619c3dfb642b5fa2fe"; + const char kClippedMD5[] = "a84cab93c102b9b9290fba3047ba702c"; + const char kTopLeftQuarterMD5[] = "f11a11137c8834389e31cf555a4a6979"; + const char kHoriStretchedMD5[] = "48ef9205941ed19691ccfa00d717187e"; + const char kRotated90ClockwiseMD5[] = "d8da2c7bf77521550d0f2752b9cf3482"; + const char kRotated180ClockwiseMD5[] = "0113386bb0bd45125bacc6dee78bfe78"; + const char kRotated270ClockwiseMD5[] = "a287e0f74ce203699cda89f9cc97a240"; + const char kMirrorHoriMD5[] = "6e8d7a6fde39d8e720fb9e620102918c"; + const char kMirrorVertMD5[] = "8f3a555ef9c0d5031831ae3715273707"; + const char kLargerTopLeftQuarterMD5[] = "172a2f4adafbadbe98017b1c025b9e27"; + const char kLargerMD5[] = "c806145641c3e6fc4e022c7065343749"; + const char kLargerClippedMD5[] = "091d3b1c7933c8f6945eb2cb41e588e9"; + const char kLargerRotatedMD5[] = "115f13353ebfc82ddb392d1f0059eb12"; + const char kLargerRotatedLandscapeMD5[] = "c901239d17d84ac84cb6f2124da71b0d"; + const char kLargerRotatedDiagonalMD5[] = "3d62417468bdaff0eb14391a0c30a3b1"; + const char kTileMD5[] = "0a190003c97220bf8877684c8d7e89cf"; + + EXPECT_TRUE(OpenDocument("rectangles.pdf")); + FPDF_PAGE page = LoadPage(0); + ASSERT_TRUE(page); + const int page_width = static_cast(FPDF_GetPageWidth(page)); + const int page_height = static_cast(FPDF_GetPageHeight(page)); + EXPECT_EQ(200, page_width); + EXPECT_EQ(300, page_height); + + std::unique_ptr bitmap = RenderLoadedPage(page); + CompareBitmap(bitmap.get(), page_width, page_height, kOriginalMD5); + + FS_RECTF page_rect{0, 0, page_width, page_height}; + + // Try rendering with an identity matrix. The output should be the same as + // the RenderLoadedPage() output. + FS_MATRIX identity_matrix{1, 0, 0, 1, 0, 0}; + TestRenderPageBitmapWithMatrix(page, page_width, page_height, identity_matrix, + page_rect, kOriginalMD5); + + // Again render with an identity matrix but with a smaller clipping rect. + FS_RECTF middle_of_page_rect{page_width / 4, page_height / 4, + page_width * 3 / 4, page_height * 3 / 4}; + TestRenderPageBitmapWithMatrix(page, page_width, page_height, identity_matrix, + middle_of_page_rect, kClippedMD5); + + // Now render again with the image scaled smaller. + FS_MATRIX half_scale_matrix{0.5, 0, 0, 0.5, 0, 0}; + TestRenderPageBitmapWithMatrix(page, page_width, page_height, + half_scale_matrix, page_rect, + kTopLeftQuarterMD5); + + // Now render again with the image scaled larger horizontally (the right half + // will be clipped). + FS_MATRIX stretch_x_matrix{2, 0, 0, 1, 0, 0}; + TestRenderPageBitmapWithMatrix(page, page_width, page_height, + stretch_x_matrix, page_rect, + kHoriStretchedMD5); + + // Try a 90 degree rotation clockwise but with the same bitmap size, so part + // will be clipped. + FS_MATRIX rotate_90_matrix{0, 1, -1, 0, page_width, 0}; + TestRenderPageBitmapWithMatrix(page, page_width, page_height, + rotate_90_matrix, page_rect, + kRotated90ClockwiseMD5); + + // 180 degree rotation clockwise. + FS_MATRIX rotate_180_matrix{-1, 0, 0, -1, page_width, page_height}; + TestRenderPageBitmapWithMatrix(page, page_width, page_height, + rotate_180_matrix, page_rect, + kRotated180ClockwiseMD5); + + // 270 degree rotation clockwise. + FS_MATRIX rotate_270_matrix{0, -1, 1, 0, 0, page_width}; + TestRenderPageBitmapWithMatrix(page, page_width, page_height, + rotate_270_matrix, page_rect, + kRotated270ClockwiseMD5); + + // Mirror horizontally. + FS_MATRIX mirror_hori_matrix{-1, 0, 0, 1, page_width, 0}; + TestRenderPageBitmapWithMatrix(page, page_width, page_height, + mirror_hori_matrix, page_rect, kMirrorHoriMD5); + + // Mirror vertically. + FS_MATRIX mirror_vert_matrix{1, 0, 0, -1, 0, page_height}; + TestRenderPageBitmapWithMatrix(page, page_width, page_height, + mirror_vert_matrix, page_rect, kMirrorVertMD5); + + // Tests rendering to a larger bitmap + const int bitmap_width = page_width * 2; + const int bitmap_height = page_height * 2; + + // Render using an identity matrix and the whole bitmap area as clipping rect. + FS_RECTF bitmap_rect{0, 0, bitmap_width, bitmap_height}; + TestRenderPageBitmapWithMatrix(page, bitmap_width, bitmap_height, + identity_matrix, bitmap_rect, + kLargerTopLeftQuarterMD5); + + // Render using a scaling matrix to fill the larger bitmap. + FS_MATRIX double_scale_matrix{2, 0, 0, 2, 0, 0}; + TestRenderPageBitmapWithMatrix(page, bitmap_width, bitmap_height, + double_scale_matrix, bitmap_rect, kLargerMD5); + + // Render the larger image again but with clipping. + FS_RECTF middle_of_bitmap_rect{bitmap_width / 4, bitmap_height / 4, + bitmap_width * 3 / 4, bitmap_height * 3 / 4}; + TestRenderPageBitmapWithMatrix(page, bitmap_width, bitmap_height, + double_scale_matrix, middle_of_bitmap_rect, + kLargerClippedMD5); + + // On the larger bitmap, try a 90 degree rotation but with the same bitmap + // size, so part will be clipped. + FS_MATRIX rotate_90_scale_2_matrix{0, 2, -2, 0, bitmap_width, 0}; + TestRenderPageBitmapWithMatrix(page, bitmap_width, bitmap_height, + rotate_90_scale_2_matrix, bitmap_rect, + kLargerRotatedMD5); + + // On the larger bitmap, apply 90 degree rotation to a bitmap with the + // appropriate dimensions. + const int landscape_bitmap_width = bitmap_height; + const int landscape_bitmap_height = bitmap_width; + FS_RECTF landscape_bitmap_rect{0, 0, landscape_bitmap_width, + landscape_bitmap_height}; + FS_MATRIX landscape_rotate_90_scale_2_matrix{ + 0, 2, -2, 0, landscape_bitmap_width, 0}; + TestRenderPageBitmapWithMatrix( + page, landscape_bitmap_width, landscape_bitmap_height, + landscape_rotate_90_scale_2_matrix, landscape_bitmap_rect, + kLargerRotatedLandscapeMD5); + + // On the larger bitmap, apply 45 degree rotation to a bitmap with the + // appropriate dimensions. + const float sqrt2 = 1.41421356f; + const int diagonal_bitmap_size = ceil((bitmap_width + bitmap_height) / sqrt2); + FS_RECTF diagonal_bitmap_rect{0, 0, diagonal_bitmap_size, + diagonal_bitmap_size}; + FS_MATRIX rotate_45_scale_2_matrix{ + sqrt2, sqrt2, -sqrt2, sqrt2, bitmap_height / sqrt2, 0}; + TestRenderPageBitmapWithMatrix(page, diagonal_bitmap_size, + diagonal_bitmap_size, rotate_45_scale_2_matrix, + diagonal_bitmap_rect, + kLargerRotatedDiagonalMD5); + + // Render the (2, 1) tile of the page (third column, second row) when the page + // is divided in 50x50 pixel tiles. The tile is scaled by a factor of 7. + const float scale = 7.0; + const int tile_size = 50; + const int tile_x = 2; + const int tile_y = 1; + int tile_bitmap_size = scale * tile_size; + FS_RECTF tile_bitmap_rect{0, 0, tile_bitmap_size, tile_bitmap_size}; + FS_MATRIX tile_2_1_matrix{scale, + 0, + 0, + scale, + -tile_x * tile_bitmap_size, + -tile_y * tile_bitmap_size}; + TestRenderPageBitmapWithMatrix(page, tile_bitmap_size, tile_bitmap_size, + tile_2_1_matrix, tile_bitmap_rect, kTileMD5); + + UnloadPage(page); +} + +class UnSupRecordDelegate : public EmbedderTest::Delegate { + public: + UnSupRecordDelegate() : type_(-1) {} + ~UnSupRecordDelegate() override {} + + void UnsupportedHandler(int type) override { type_ = type; } + + int type_; +}; + +TEST_F(FPDFViewEmbeddertest, UnSupportedOperations_NotFound) { + UnSupRecordDelegate delegate; + SetDelegate(&delegate); + ASSERT_TRUE(OpenDocument("hello_world.pdf")); + EXPECT_EQ(delegate.type_, -1); + SetDelegate(nullptr); +} + +TEST_F(FPDFViewEmbeddertest, UnSupportedOperations_LoadCustomDocument) { + UnSupRecordDelegate delegate; + SetDelegate(&delegate); + ASSERT_TRUE(OpenDocument("unsupported_feature.pdf")); + EXPECT_EQ(FPDF_UNSP_DOC_PORTABLECOLLECTION, delegate.type_); + SetDelegate(nullptr); +} + +TEST_F(FPDFViewEmbeddertest, UnSupportedOperations_LoadDocument) { + std::string file_path; + ASSERT_TRUE( + PathService::GetTestFilePath("unsupported_feature.pdf", &file_path)); + + UnSupRecordDelegate delegate; + SetDelegate(&delegate); + FPDF_DOCUMENT doc = FPDF_LoadDocument(file_path.c_str(), ""); + EXPECT_TRUE(doc != nullptr); + EXPECT_EQ(FPDF_UNSP_DOC_PORTABLECOLLECTION, delegate.type_); + FPDF_CloseDocument(doc); + SetDelegate(nullptr); +} diff --git a/fpdfsdk/fpdfannot.cpp b/fpdfsdk/fpdfannot.cpp deleted file mode 100644 index 2ab0bca454..0000000000 --- a/fpdfsdk/fpdfannot.cpp +++ /dev/null @@ -1,887 +0,0 @@ -// Copyright 2017 PDFium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#include "public/fpdf_annot.h" - -#include -#include - -#include "core/fpdfapi/edit/cpdf_pagecontentgenerator.h" -#include "core/fpdfapi/page/cpdf_form.h" -#include "core/fpdfapi/page/cpdf_page.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_document.h" -#include "core/fpdfapi/parser/cpdf_name.h" -#include "core/fpdfapi/parser/cpdf_number.h" -#include "core/fpdfapi/parser/cpdf_string.h" -#include "core/fpdfdoc/cpdf_annot.h" -#include "core/fpdfdoc/cpdf_formfield.h" -#include "core/fpdfdoc/cpdf_interform.h" -#include "core/fpdfdoc/cpvt_generateap.h" -#include "core/fxge/cfx_color.h" -#include "fpdfsdk/cpdfsdk_helpers.h" - -namespace { - -// These checks ensure the consistency of annotation subtype values across core/ -// and public. -static_assert(static_cast(CPDF_Annot::Subtype::UNKNOWN) == - FPDF_ANNOT_UNKNOWN, - "CPDF_Annot::UNKNOWN value mismatch"); -static_assert(static_cast(CPDF_Annot::Subtype::TEXT) == FPDF_ANNOT_TEXT, - "CPDF_Annot::TEXT value mismatch"); -static_assert(static_cast(CPDF_Annot::Subtype::LINK) == FPDF_ANNOT_LINK, - "CPDF_Annot::LINK value mismatch"); -static_assert(static_cast(CPDF_Annot::Subtype::FREETEXT) == - FPDF_ANNOT_FREETEXT, - "CPDF_Annot::FREETEXT value mismatch"); -static_assert(static_cast(CPDF_Annot::Subtype::LINE) == FPDF_ANNOT_LINE, - "CPDF_Annot::LINE value mismatch"); -static_assert(static_cast(CPDF_Annot::Subtype::SQUARE) == - FPDF_ANNOT_SQUARE, - "CPDF_Annot::SQUARE value mismatch"); -static_assert(static_cast(CPDF_Annot::Subtype::CIRCLE) == - FPDF_ANNOT_CIRCLE, - "CPDF_Annot::CIRCLE value mismatch"); -static_assert(static_cast(CPDF_Annot::Subtype::POLYGON) == - FPDF_ANNOT_POLYGON, - "CPDF_Annot::POLYGON value mismatch"); -static_assert(static_cast(CPDF_Annot::Subtype::POLYLINE) == - FPDF_ANNOT_POLYLINE, - "CPDF_Annot::POLYLINE value mismatch"); -static_assert(static_cast(CPDF_Annot::Subtype::HIGHLIGHT) == - FPDF_ANNOT_HIGHLIGHT, - "CPDF_Annot::HIGHLIGHT value mismatch"); -static_assert(static_cast(CPDF_Annot::Subtype::UNDERLINE) == - FPDF_ANNOT_UNDERLINE, - "CPDF_Annot::UNDERLINE value mismatch"); -static_assert(static_cast(CPDF_Annot::Subtype::SQUIGGLY) == - FPDF_ANNOT_SQUIGGLY, - "CPDF_Annot::SQUIGGLY value mismatch"); -static_assert(static_cast(CPDF_Annot::Subtype::STRIKEOUT) == - FPDF_ANNOT_STRIKEOUT, - "CPDF_Annot::STRIKEOUT value mismatch"); -static_assert(static_cast(CPDF_Annot::Subtype::STAMP) == FPDF_ANNOT_STAMP, - "CPDF_Annot::STAMP value mismatch"); -static_assert(static_cast(CPDF_Annot::Subtype::CARET) == FPDF_ANNOT_CARET, - "CPDF_Annot::CARET value mismatch"); -static_assert(static_cast(CPDF_Annot::Subtype::INK) == FPDF_ANNOT_INK, - "CPDF_Annot::INK value mismatch"); -static_assert(static_cast(CPDF_Annot::Subtype::POPUP) == FPDF_ANNOT_POPUP, - "CPDF_Annot::POPUP value mismatch"); -static_assert(static_cast(CPDF_Annot::Subtype::FILEATTACHMENT) == - FPDF_ANNOT_FILEATTACHMENT, - "CPDF_Annot::FILEATTACHMENT value mismatch"); -static_assert(static_cast(CPDF_Annot::Subtype::SOUND) == FPDF_ANNOT_SOUND, - "CPDF_Annot::SOUND value mismatch"); -static_assert(static_cast(CPDF_Annot::Subtype::MOVIE) == FPDF_ANNOT_MOVIE, - "CPDF_Annot::MOVIE value mismatch"); -static_assert(static_cast(CPDF_Annot::Subtype::WIDGET) == - FPDF_ANNOT_WIDGET, - "CPDF_Annot::WIDGET value mismatch"); -static_assert(static_cast(CPDF_Annot::Subtype::SCREEN) == - FPDF_ANNOT_SCREEN, - "CPDF_Annot::SCREEN value mismatch"); -static_assert(static_cast(CPDF_Annot::Subtype::PRINTERMARK) == - FPDF_ANNOT_PRINTERMARK, - "CPDF_Annot::PRINTERMARK value mismatch"); -static_assert(static_cast(CPDF_Annot::Subtype::TRAPNET) == - FPDF_ANNOT_TRAPNET, - "CPDF_Annot::TRAPNET value mismatch"); -static_assert(static_cast(CPDF_Annot::Subtype::WATERMARK) == - FPDF_ANNOT_WATERMARK, - "CPDF_Annot::WATERMARK value mismatch"); -static_assert(static_cast(CPDF_Annot::Subtype::THREED) == - FPDF_ANNOT_THREED, - "CPDF_Annot::THREED value mismatch"); -static_assert(static_cast(CPDF_Annot::Subtype::RICHMEDIA) == - FPDF_ANNOT_RICHMEDIA, - "CPDF_Annot::RICHMEDIA value mismatch"); -static_assert(static_cast(CPDF_Annot::Subtype::XFAWIDGET) == - FPDF_ANNOT_XFAWIDGET, - "CPDF_Annot::XFAWIDGET value mismatch"); - -// These checks ensure the consistency of annotation appearance mode values -// across core/ and public. -static_assert(static_cast(CPDF_Annot::AppearanceMode::Normal) == - FPDF_ANNOT_APPEARANCEMODE_NORMAL, - "CPDF_Annot::AppearanceMode::Normal value mismatch"); -static_assert(static_cast(CPDF_Annot::AppearanceMode::Rollover) == - FPDF_ANNOT_APPEARANCEMODE_ROLLOVER, - "CPDF_Annot::AppearanceMode::Rollover value mismatch"); -static_assert(static_cast(CPDF_Annot::AppearanceMode::Down) == - FPDF_ANNOT_APPEARANCEMODE_DOWN, - "CPDF_Annot::AppearanceMode::Down value mismatch"); - -// These checks ensure the consistency of dictionary value types across core/ -// and public/. -static_assert(static_cast(CPDF_Object::Type::BOOLEAN) == - FPDF_OBJECT_BOOLEAN, - "CPDF_Object::BOOLEAN value mismatch"); -static_assert(static_cast(CPDF_Object::Type::NUMBER) == FPDF_OBJECT_NUMBER, - "CPDF_Object::NUMBER value mismatch"); -static_assert(static_cast(CPDF_Object::Type::STRING) == FPDF_OBJECT_STRING, - "CPDF_Object::STRING value mismatch"); -static_assert(static_cast(CPDF_Object::Type::NAME) == FPDF_OBJECT_NAME, - "CPDF_Object::NAME value mismatch"); -static_assert(static_cast(CPDF_Object::Type::ARRAY) == FPDF_OBJECT_ARRAY, - "CPDF_Object::ARRAY value mismatch"); -static_assert(static_cast(CPDF_Object::Type::DICTIONARY) == - FPDF_OBJECT_DICTIONARY, - "CPDF_Object::DICTIONARY value mismatch"); -static_assert(static_cast(CPDF_Object::Type::STREAM) == FPDF_OBJECT_STREAM, - "CPDF_Object::STREAM value mismatch"); -static_assert(static_cast(CPDF_Object::Type::NULLOBJ) == - FPDF_OBJECT_NULLOBJ, - "CPDF_Object::NULLOBJ value mismatch"); -static_assert(static_cast(CPDF_Object::Type::REFERENCE) == - FPDF_OBJECT_REFERENCE, - "CPDF_Object::REFERENCE value mismatch"); - -class CPDF_AnnotContext { - public: - CPDF_AnnotContext(CPDF_Dictionary* pAnnotDict, - CPDF_Page* pPage, - CPDF_Stream* pStream) - : m_pAnnotDict(pAnnotDict), m_pPage(pPage) { - SetForm(pStream); - } - ~CPDF_AnnotContext() {} - - bool HasForm() const { return !!m_pAnnotForm; } - - void SetForm(CPDF_Stream* pStream) { - if (!pStream) - return; - - // Reset the annotation matrix to be the identity matrix, since the - // appearance stream already takes matrix into account. - pStream->GetDict()->SetMatrixFor("Matrix", CFX_Matrix()); - - m_pAnnotForm = pdfium::MakeUnique( - m_pPage->m_pDocument.Get(), m_pPage->m_pResources.Get(), pStream); - m_pAnnotForm->ParseContent(); - } - - CPDF_Form* GetForm() const { return m_pAnnotForm.get(); } - CPDF_Dictionary* GetAnnotDict() const { return m_pAnnotDict.Get(); } - CPDF_Page* GetPage() const { return m_pPage.Get(); } - - private: - std::unique_ptr m_pAnnotForm; - UnownedPtr m_pAnnotDict; - UnownedPtr m_pPage; -}; - -CPDF_AnnotContext* CPDFAnnotContextFromFPDFAnnotation(FPDF_ANNOTATION annot) { - return static_cast(annot); -} - -bool HasAPStream(const CPDF_Dictionary* pAnnotDict) { - return !!FPDFDOC_GetAnnotAP(pAnnotDict, CPDF_Annot::AppearanceMode::Normal); -} - -void UpdateContentStream(CPDF_Form* pForm, CPDF_Stream* pStream) { - ASSERT(pForm); - ASSERT(pStream); - - CPDF_PageContentGenerator generator(pForm); - std::ostringstream buf; - generator.ProcessPageObjects(&buf); - pStream->SetDataAndRemoveFilter(&buf); -} - -} // namespace - -FPDF_EXPORT FPDF_BOOL FPDF_CALLCONV -FPDFAnnot_IsSupportedSubtype(FPDF_ANNOTATION_SUBTYPE subtype) { - // The supported subtypes must also be communicated in the user doc. - return subtype == FPDF_ANNOT_CIRCLE || subtype == FPDF_ANNOT_FREETEXT || - subtype == FPDF_ANNOT_HIGHLIGHT || subtype == FPDF_ANNOT_INK || - subtype == FPDF_ANNOT_POPUP || subtype == FPDF_ANNOT_SQUARE || - subtype == FPDF_ANNOT_SQUIGGLY || subtype == FPDF_ANNOT_STAMP || - subtype == FPDF_ANNOT_STRIKEOUT || subtype == FPDF_ANNOT_TEXT || - subtype == FPDF_ANNOT_UNDERLINE; -} - -FPDF_EXPORT FPDF_ANNOTATION FPDF_CALLCONV -FPDFPage_CreateAnnot(FPDF_PAGE page, FPDF_ANNOTATION_SUBTYPE subtype) { - CPDF_Page* pPage = CPDFPageFromFPDFPage(page); - if (!pPage || !FPDFAnnot_IsSupportedSubtype(subtype)) - return nullptr; - - auto pDict = pdfium::MakeUnique( - pPage->m_pDocument->GetByteStringPool()); - pDict->SetNewFor("Type", "Annot"); - pDict->SetNewFor("Subtype", - CPDF_Annot::AnnotSubtypeToString( - static_cast(subtype))); - auto pNewAnnot = - pdfium::MakeUnique(pDict.get(), pPage, nullptr); - - CPDF_Array* pAnnotList = pPage->m_pFormDict->GetArrayFor("Annots"); - if (!pAnnotList) - pAnnotList = pPage->m_pFormDict->SetNewFor("Annots"); - - pAnnotList->Add(std::move(pDict)); - return pNewAnnot.release(); -} - -FPDF_EXPORT int FPDF_CALLCONV FPDFPage_GetAnnotCount(FPDF_PAGE page) { - CPDF_Page* pPage = CPDFPageFromFPDFPage(page); - if (!pPage || !pPage->m_pFormDict) - return 0; - - CPDF_Array* pAnnots = pPage->m_pFormDict->GetArrayFor("Annots"); - return pAnnots ? pAnnots->GetCount() : 0; -} - -FPDF_EXPORT FPDF_ANNOTATION FPDF_CALLCONV FPDFPage_GetAnnot(FPDF_PAGE page, - int index) { - CPDF_Page* pPage = CPDFPageFromFPDFPage(page); - if (!pPage || !pPage->m_pFormDict || index < 0) - return nullptr; - - CPDF_Array* pAnnots = pPage->m_pFormDict->GetArrayFor("Annots"); - if (!pAnnots || static_cast(index) >= pAnnots->GetCount()) - return nullptr; - - CPDF_Dictionary* pDict = ToDictionary(pAnnots->GetDirectObjectAt(index)); - auto pNewAnnot = pdfium::MakeUnique(pDict, pPage, nullptr); - return pNewAnnot.release(); -} - -FPDF_EXPORT int FPDF_CALLCONV FPDFPage_GetAnnotIndex(FPDF_PAGE page, - FPDF_ANNOTATION annot) { - CPDF_Page* pPage = CPDFPageFromFPDFPage(page); - CPDF_AnnotContext* pAnnot = CPDFAnnotContextFromFPDFAnnotation(annot); - if (!pPage || !pPage->m_pFormDict || !pAnnot || !pAnnot->GetAnnotDict()) - return -1; - - CPDF_Array* pAnnots = pPage->m_pFormDict->GetArrayFor("Annots"); - if (!pAnnots) - return -1; - - CPDF_Dictionary* pDict = pAnnot->GetAnnotDict(); - auto it = - std::find_if(pAnnots->begin(), pAnnots->end(), - [pDict](const std::unique_ptr& candidate) { - return candidate->GetDirect() == pDict; - }); - - if (it == pAnnots->end()) - return -1; - - return it - pAnnots->begin(); -} - -FPDF_EXPORT void FPDF_CALLCONV FPDFPage_CloseAnnot(FPDF_ANNOTATION annot) { - delete CPDFAnnotContextFromFPDFAnnotation(annot); -} - -FPDF_EXPORT FPDF_BOOL FPDF_CALLCONV FPDFPage_RemoveAnnot(FPDF_PAGE page, - int index) { - CPDF_Page* pPage = CPDFPageFromFPDFPage(page); - if (!pPage || !pPage->m_pFormDict || index < 0) - return false; - - CPDF_Array* pAnnots = pPage->m_pFormDict->GetArrayFor("Annots"); - if (!pAnnots || static_cast(index) >= pAnnots->GetCount()) - return false; - - pAnnots->RemoveAt(index); - return true; -} - -FPDF_EXPORT FPDF_ANNOTATION_SUBTYPE FPDF_CALLCONV -FPDFAnnot_GetSubtype(FPDF_ANNOTATION annot) { - if (!annot) - return FPDF_ANNOT_UNKNOWN; - - CPDF_Dictionary* pAnnotDict = - CPDFAnnotContextFromFPDFAnnotation(annot)->GetAnnotDict(); - if (!pAnnotDict) - return FPDF_ANNOT_UNKNOWN; - - return static_cast( - CPDF_Annot::StringToAnnotSubtype(pAnnotDict->GetStringFor("Subtype"))); -} - -FPDF_EXPORT FPDF_BOOL FPDF_CALLCONV -FPDFAnnot_IsObjectSupportedSubtype(FPDF_ANNOTATION_SUBTYPE subtype) { - // The supported subtypes must also be communicated in the user doc. - return subtype == FPDF_ANNOT_INK || subtype == FPDF_ANNOT_STAMP; -} - -FPDF_EXPORT FPDF_BOOL FPDF_CALLCONV -FPDFAnnot_UpdateObject(FPDF_ANNOTATION annot, FPDF_PAGEOBJECT obj) { - CPDF_AnnotContext* pAnnot = CPDFAnnotContextFromFPDFAnnotation(annot); - CPDF_PageObject* pObj = CPDFPageObjectFromFPDFPageObject(obj); - if (!pAnnot || !pAnnot->GetAnnotDict() || !pAnnot->HasForm() || !pObj) - return false; - - // Check that the annotation type is supported by this method. - if (!FPDFAnnot_IsObjectSupportedSubtype(FPDFAnnot_GetSubtype(annot))) - return false; - - // Check that the annotation already has an appearance stream, since an - // existing object is to be updated. - CPDF_Stream* pStream = FPDFDOC_GetAnnotAP(pAnnot->GetAnnotDict(), - CPDF_Annot::AppearanceMode::Normal); - if (!pStream) - return false; - - // Check that the object is already in this annotation's object list. - CPDF_Form* pForm = pAnnot->GetForm(); - CPDF_PageObjectList* pObjList = pForm->GetPageObjectList(); - auto it = - std::find_if(pObjList->begin(), pObjList->end(), - [pObj](const std::unique_ptr& candidate) { - return candidate.get() == pObj; - }); - if (it == pObjList->end()) - return false; - - // Update the content stream data in the annotation's AP stream. - UpdateContentStream(pForm, pStream); - return true; -} - -FPDF_EXPORT FPDF_BOOL FPDF_CALLCONV -FPDFAnnot_AppendObject(FPDF_ANNOTATION annot, FPDF_PAGEOBJECT obj) { - CPDF_AnnotContext* pAnnot = CPDFAnnotContextFromFPDFAnnotation(annot); - CPDF_PageObject* pObj = CPDFPageObjectFromFPDFPageObject(obj); - if (!pAnnot || !pObj) - return false; - - CPDF_Dictionary* pAnnotDict = pAnnot->GetAnnotDict(); - CPDF_Page* pPage = pAnnot->GetPage(); - if (!pAnnotDict || !pPage) - return false; - - // Check that the annotation type is supported by this method. - if (!FPDFAnnot_IsObjectSupportedSubtype(FPDFAnnot_GetSubtype(annot))) - return false; - - // If the annotation does not have an AP stream yet, generate and set it. - CPDF_Stream* pStream = FPDFDOC_GetAnnotAP(pAnnot->GetAnnotDict(), - CPDF_Annot::AppearanceMode::Normal); - if (!pStream) { - CPVT_GenerateAP::GenerateEmptyAP(pPage->m_pDocument.Get(), pAnnotDict); - pStream = - FPDFDOC_GetAnnotAP(pAnnotDict, CPDF_Annot::AppearanceMode::Normal); - if (!pStream) - return false; - } - - // Get the annotation's corresponding form object for parsing its AP stream. - if (!pAnnot->HasForm()) - pAnnot->SetForm(pStream); - - // Check that the object did not come from the same annotation. If this check - // succeeds, then it is assumed that the object came from - // FPDFPageObj_CreateNew{Path|Rect}() or FPDFPageObj_New{Text|Image}Obj(). - // Note that an object that came from a different annotation must not be - // passed here, since an object cannot belong to more than one annotation. - CPDF_Form* pForm = pAnnot->GetForm(); - CPDF_PageObjectList* pObjList = pForm->GetPageObjectList(); - auto it = - std::find_if(pObjList->begin(), pObjList->end(), - [pObj](const std::unique_ptr& candidate) { - return candidate.get() == pObj; - }); - if (it != pObjList->end()) - return false; - - // Append the object to the object list. - std::unique_ptr pPageObjHolder(pObj); - pObjList->push_back(std::move(pPageObjHolder)); - - // Set the content stream data in the annotation's AP stream. - UpdateContentStream(pForm, pStream); - return true; -} - -FPDF_EXPORT int FPDF_CALLCONV FPDFAnnot_GetObjectCount(FPDF_ANNOTATION annot) { - CPDF_AnnotContext* pAnnot = CPDFAnnotContextFromFPDFAnnotation(annot); - if (!pAnnot || !pAnnot->GetAnnotDict()) - return 0; - - if (!pAnnot->HasForm()) { - CPDF_Stream* pStream = FPDFDOC_GetAnnotAP( - pAnnot->GetAnnotDict(), CPDF_Annot::AppearanceMode::Normal); - if (!pStream) - return 0; - - pAnnot->SetForm(pStream); - } - return pdfium::CollectionSize(*pAnnot->GetForm()->GetPageObjectList()); -} - -FPDF_EXPORT FPDF_PAGEOBJECT FPDF_CALLCONV -FPDFAnnot_GetObject(FPDF_ANNOTATION annot, int index) { - CPDF_AnnotContext* pAnnot = CPDFAnnotContextFromFPDFAnnotation(annot); - if (!pAnnot || !pAnnot->GetAnnotDict() || index < 0) - return nullptr; - - if (!pAnnot->HasForm()) { - CPDF_Stream* pStream = FPDFDOC_GetAnnotAP( - pAnnot->GetAnnotDict(), CPDF_Annot::AppearanceMode::Normal); - if (!pStream) - return nullptr; - - pAnnot->SetForm(pStream); - } - - return pAnnot->GetForm()->GetPageObjectList()->GetPageObjectByIndex(index); -} - -FPDF_EXPORT FPDF_BOOL FPDF_CALLCONV -FPDFAnnot_RemoveObject(FPDF_ANNOTATION annot, int index) { - CPDF_AnnotContext* pAnnot = CPDFAnnotContextFromFPDFAnnotation(annot); - if (!pAnnot || !pAnnot->GetAnnotDict() || !pAnnot->HasForm() || index < 0) - return false; - - // Check that the annotation type is supported by this method. - if (!FPDFAnnot_IsObjectSupportedSubtype(FPDFAnnot_GetSubtype(annot))) - return false; - - // Check that the annotation already has an appearance stream, since an - // existing object is to be deleted. - CPDF_Stream* pStream = FPDFDOC_GetAnnotAP(pAnnot->GetAnnotDict(), - CPDF_Annot::AppearanceMode::Normal); - if (!pStream) - return false; - - CPDF_PageObjectList* pObjList = pAnnot->GetForm()->GetPageObjectList(); - if (static_cast(index) >= pObjList->size()) - return false; - - pObjList->erase(pObjList->begin() + index); - UpdateContentStream(pAnnot->GetForm(), pStream); - return true; -} - -FPDF_EXPORT FPDF_BOOL FPDF_CALLCONV FPDFAnnot_SetColor(FPDF_ANNOTATION annot, - FPDFANNOT_COLORTYPE type, - unsigned int R, - unsigned int G, - unsigned int B, - unsigned int A) { - if (!annot || R > 255 || G > 255 || B > 255 || A > 255) - return false; - - CPDF_Dictionary* pAnnotDict = - CPDFAnnotContextFromFPDFAnnotation(annot)->GetAnnotDict(); - if (!pAnnotDict) - return false; - - // For annotations with their appearance streams already defined, the path - // stream's own color definitions take priority over the annotation color - // definitions set by this method, hence this method will simply fail. - if (HasAPStream(pAnnotDict)) - return false; - - // Set the opacity of the annotation. - pAnnotDict->SetNewFor("CA", A / 255.f); - - // Set the color of the annotation. - ByteString key = type == FPDFANNOT_COLORTYPE_InteriorColor ? "IC" : "C"; - CPDF_Array* pColor = pAnnotDict->GetArrayFor(key); - if (pColor) - pColor->Clear(); - else - pColor = pAnnotDict->SetNewFor(key); - - pColor->AddNew(R / 255.f); - pColor->AddNew(G / 255.f); - pColor->AddNew(B / 255.f); - - return true; -} - -FPDF_EXPORT FPDF_BOOL FPDF_CALLCONV FPDFAnnot_GetColor(FPDF_ANNOTATION annot, - FPDFANNOT_COLORTYPE type, - unsigned int* R, - unsigned int* G, - unsigned int* B, - unsigned int* A) { - if (!annot || !R || !G || !B || !A) - return false; - - CPDF_Dictionary* pAnnotDict = - CPDFAnnotContextFromFPDFAnnotation(annot)->GetAnnotDict(); - if (!pAnnotDict) - return false; - - // For annotations with their appearance streams already defined, the path - // stream's own color definitions take priority over the annotation color - // definitions retrieved by this method, hence this method will simply fail. - if (HasAPStream(pAnnotDict)) - return false; - - CPDF_Array* pColor = pAnnotDict->GetArrayFor( - type == FPDFANNOT_COLORTYPE_InteriorColor ? "IC" : "C"); - *A = - (pAnnotDict->KeyExist("CA") ? pAnnotDict->GetNumberFor("CA") : 1) * 255.f; - if (!pColor) { - // Use default color. The default colors must be consistent with the ones - // used to generate AP. See calls to GetColorStringWithDefault() in - // CPVT_GenerateAP::Generate*AP(). - if (pAnnotDict->GetStringFor("Subtype") == "Highlight") { - *R = 255; - *G = 255; - *B = 0; - } else { - *R = 0; - *G = 0; - *B = 0; - } - return true; - } - - CFX_Color color = CFX_Color::ParseColor(*pColor); - switch (color.nColorType) { - case CFX_Color::kRGB: - *R = color.fColor1 * 255.f; - *G = color.fColor2 * 255.f; - *B = color.fColor3 * 255.f; - break; - case CFX_Color::kGray: - *R = 255.f * color.fColor1; - *G = 255.f * color.fColor1; - *B = 255.f * color.fColor1; - break; - case CFX_Color::kCMYK: - *R = 255.f * (1 - color.fColor1) * (1 - color.fColor4); - *G = 255.f * (1 - color.fColor2) * (1 - color.fColor4); - *B = 255.f * (1 - color.fColor3) * (1 - color.fColor4); - break; - case CFX_Color::kTransparent: - *R = 0; - *G = 0; - *B = 0; - break; - } - return true; -} - -FPDF_EXPORT FPDF_BOOL FPDF_CALLCONV -FPDFAnnot_HasAttachmentPoints(FPDF_ANNOTATION annot) { - if (!annot) - return false; - - FPDF_ANNOTATION_SUBTYPE subtype = FPDFAnnot_GetSubtype(annot); - return subtype == FPDF_ANNOT_LINK || subtype == FPDF_ANNOT_HIGHLIGHT || - subtype == FPDF_ANNOT_UNDERLINE || subtype == FPDF_ANNOT_SQUIGGLY || - subtype == FPDF_ANNOT_STRIKEOUT; -} - -FPDF_EXPORT FPDF_BOOL FPDF_CALLCONV -FPDFAnnot_SetAttachmentPoints(FPDF_ANNOTATION annot, - const FS_QUADPOINTSF* quad_points) { - if (!FPDFAnnot_HasAttachmentPoints(annot) || !quad_points) - return false; - - CPDF_Dictionary* pAnnotDict = - CPDFAnnotContextFromFPDFAnnotation(annot)->GetAnnotDict(); - if (!pAnnotDict) - return false; - - // Update the "QuadPoints" entry in the annotation dictionary. - CPDF_Array* pQuadPoints = pAnnotDict->GetArrayFor("QuadPoints"); - if (pQuadPoints) - pQuadPoints->Clear(); - else - pQuadPoints = pAnnotDict->SetNewFor("QuadPoints"); - - pQuadPoints->AddNew(quad_points->x1); - pQuadPoints->AddNew(quad_points->y1); - pQuadPoints->AddNew(quad_points->x2); - pQuadPoints->AddNew(quad_points->y2); - pQuadPoints->AddNew(quad_points->x3); - pQuadPoints->AddNew(quad_points->y3); - pQuadPoints->AddNew(quad_points->x4); - pQuadPoints->AddNew(quad_points->y4); - - // If the annotation's appearance stream is defined, and the new quadpoints - // defines a bigger bounding box than the appearance stream currently - // specifies, then update the "BBox" entry in the AP dictionary too, since it - // comes from annotation dictionary's "QuadPoints" entry. - CPDF_Stream* pStream = - FPDFDOC_GetAnnotAP(pAnnotDict, CPDF_Annot::AppearanceMode::Normal); - if (pStream) { - CFX_FloatRect newRect = CPDF_Annot::BoundingRectFromQuadPoints(pAnnotDict); - if (newRect.Contains(pStream->GetDict()->GetRectFor("BBox"))) - pStream->GetDict()->SetRectFor("BBox", newRect); - } - return true; -} - -FPDF_EXPORT size_t FPDF_CALLCONV -FPDFAnnot_CountAttachmentPoints(FPDF_ANNOTATION annot) { - if (!FPDFAnnot_HasAttachmentPoints(annot)) - return 0; - - CPDF_Dictionary* pAnnotDict = - CPDFAnnotContextFromFPDFAnnotation(annot)->GetAnnotDict(); - const CPDF_Array* pArray = GetQuadPointsArrayFromDictionary(pAnnotDict); - return pArray ? pArray->GetCount() / 8 : 0; -} - -FPDF_EXPORT FPDF_BOOL FPDF_CALLCONV -FPDFAnnot_GetAttachmentPoints(FPDF_ANNOTATION annot, - FS_QUADPOINTSF* quad_points) { - if (!FPDFAnnot_HasAttachmentPoints(annot) || !quad_points) - return false; - - return GetQuadPointsFromDictionary( - CPDFAnnotContextFromFPDFAnnotation(annot)->GetAnnotDict(), 0, - quad_points); -} - -FPDF_EXPORT FPDF_BOOL FPDF_CALLCONV FPDFAnnot_SetRect(FPDF_ANNOTATION annot, - const FS_RECTF* rect) { - if (!annot || !rect) - return false; - - CPDF_Dictionary* pAnnotDict = - CPDFAnnotContextFromFPDFAnnotation(annot)->GetAnnotDict(); - if (!pAnnotDict) - return false; - - CFX_FloatRect newRect = CFXFloatRectFromFSRECTF(*rect); - - // Update the "Rect" entry in the annotation dictionary. - pAnnotDict->SetRectFor("Rect", newRect); - - // If the annotation's appearance stream is defined, the annotation is of a - // type that does not have quadpoints, and the new rectangle is bigger than - // the current bounding box, then update the "BBox" entry in the AP - // dictionary too, since its "BBox" entry comes from annotation dictionary's - // "Rect" entry. - if (FPDFAnnot_HasAttachmentPoints(annot)) - return true; - - CPDF_Stream* pStream = - FPDFDOC_GetAnnotAP(pAnnotDict, CPDF_Annot::AppearanceMode::Normal); - if (pStream && newRect.Contains(pStream->GetDict()->GetRectFor("BBox"))) - pStream->GetDict()->SetRectFor("BBox", newRect); - return true; -} - -FPDF_EXPORT FPDF_BOOL FPDF_CALLCONV FPDFAnnot_GetRect(FPDF_ANNOTATION annot, - FS_RECTF* rect) { - if (!annot || !rect) - return false; - - CPDF_Dictionary* pAnnotDict = - CPDFAnnotContextFromFPDFAnnotation(annot)->GetAnnotDict(); - if (!pAnnotDict) - return false; - - FSRECTFFromCFXFloatRect(pAnnotDict->GetRectFor("Rect"), rect); - return true; -} - -FPDF_EXPORT FPDF_BOOL FPDF_CALLCONV FPDFAnnot_HasKey(FPDF_ANNOTATION annot, - FPDF_BYTESTRING key) { - if (!annot) - return false; - - CPDF_Dictionary* pAnnotDict = - CPDFAnnotContextFromFPDFAnnotation(annot)->GetAnnotDict(); - if (!pAnnotDict) - return false; - - return pAnnotDict->KeyExist(key); -} - -FPDF_EXPORT FPDF_OBJECT_TYPE FPDF_CALLCONV -FPDFAnnot_GetValueType(FPDF_ANNOTATION annot, FPDF_BYTESTRING key) { - if (!FPDFAnnot_HasKey(annot, key)) - return FPDF_OBJECT_UNKNOWN; - - auto* pAnnot = CPDFAnnotContextFromFPDFAnnotation(annot); - CPDF_Object* pObj = pAnnot->GetAnnotDict()->GetObjectFor(key); - return pObj ? pObj->GetType() : FPDF_OBJECT_UNKNOWN; -} - -FPDF_EXPORT FPDF_BOOL FPDF_CALLCONV -FPDFAnnot_SetStringValue(FPDF_ANNOTATION annot, - FPDF_BYTESTRING key, - FPDF_WIDESTRING value) { - if (!annot) - return false; - - CPDF_Dictionary* pAnnotDict = - CPDFAnnotContextFromFPDFAnnotation(annot)->GetAnnotDict(); - if (!pAnnotDict) - return false; - - pAnnotDict->SetNewFor( - key, CFXByteStringFromFPDFWideString(value), false); - return true; -} - -FPDF_EXPORT unsigned long FPDF_CALLCONV -FPDFAnnot_GetStringValue(FPDF_ANNOTATION annot, - FPDF_BYTESTRING key, - void* buffer, - unsigned long buflen) { - if (!annot) - return 0; - - CPDF_Dictionary* pAnnotDict = - CPDFAnnotContextFromFPDFAnnotation(annot)->GetAnnotDict(); - if (!pAnnotDict) - return 0; - - return Utf16EncodeMaybeCopyAndReturnLength(pAnnotDict->GetUnicodeTextFor(key), - buffer, buflen); -} - -FPDF_EXPORT FPDF_BOOL FPDF_CALLCONV -FPDFAnnot_SetAP(FPDF_ANNOTATION annot, - FPDF_ANNOT_APPEARANCEMODE appearanceMode, - FPDF_WIDESTRING value) { - if (appearanceMode < 0 || appearanceMode >= FPDF_ANNOT_APPEARANCEMODE_COUNT) - return false; - - if (!annot) - return false; - - CPDF_Dictionary* pAnnotDict = - CPDFAnnotContextFromFPDFAnnotation(annot)->GetAnnotDict(); - if (!pAnnotDict) - return false; - - constexpr const char* modeKeyForMode[] = {"N", "R", "D"}; - static_assert(FX_ArraySize(modeKeyForMode) == FPDF_ANNOT_APPEARANCEMODE_COUNT, - "length of modeKeyForMode should be equal to " - "FPDF_ANNOT_APPEARANCEMODE_COUNT"); - const char* modeKey = modeKeyForMode[appearanceMode]; - - CPDF_Dictionary* pApDict = pAnnotDict->GetDictFor("AP"); - - // If value is null, we're in remove mode. Otherwise, we're in add/update - // mode. - if (value) { - if (!pApDict) - pApDict = pAnnotDict->SetNewFor("AP"); - - ByteString newValue = CFXByteStringFromFPDFWideString(value); - auto pNewApStream = pdfium::MakeUnique(); - pNewApStream->SetData(newValue.raw_str(), newValue.GetLength()); - pApDict->SetFor(modeKey, std::move(pNewApStream)); - } else { - if (pApDict) { - if (appearanceMode == FPDF_ANNOT_APPEARANCEMODE_NORMAL) - pAnnotDict->RemoveFor("AP"); - else - pApDict->RemoveFor(modeKey); - } - } - - return true; -} - -FPDF_EXPORT unsigned long FPDF_CALLCONV -FPDFAnnot_GetAP(FPDF_ANNOTATION annot, - FPDF_ANNOT_APPEARANCEMODE appearanceMode, - void* buffer, - unsigned long buflen) { - if (appearanceMode < 0 || appearanceMode >= FPDF_ANNOT_APPEARANCEMODE_COUNT) - return 0; - - if (!annot) - return 0; - - CPDF_Dictionary* pAnnotDict = - CPDFAnnotContextFromFPDFAnnotation(annot)->GetAnnotDict(); - if (!pAnnotDict) - return 0; - - CPDF_Annot::AppearanceMode mode = - static_cast(appearanceMode); - - CPDF_Stream* pStream = FPDFDOC_GetAnnotAPNoFallback(pAnnotDict, mode); - return Utf16EncodeMaybeCopyAndReturnLength( - pStream ? pStream->GetUnicodeText() : L"", buffer, buflen); -} - -FPDF_EXPORT FPDF_ANNOTATION FPDF_CALLCONV -FPDFAnnot_GetLinkedAnnot(FPDF_ANNOTATION annot, FPDF_BYTESTRING key) { - CPDF_AnnotContext* pAnnot = CPDFAnnotContextFromFPDFAnnotation(annot); - if (!pAnnot || !pAnnot->GetAnnotDict()) - return nullptr; - - CPDF_Dictionary* pLinkedDict = pAnnot->GetAnnotDict()->GetDictFor(key); - if (!pLinkedDict || pLinkedDict->GetStringFor("Type") != "Annot") - return nullptr; - - auto pLinkedAnnot = pdfium::MakeUnique( - pLinkedDict, pAnnot->GetPage(), nullptr); - return pLinkedAnnot.release(); -} - -FPDF_EXPORT int FPDF_CALLCONV FPDFAnnot_GetFlags(FPDF_ANNOTATION annot) { - if (!annot) - return FPDF_ANNOT_FLAG_NONE; - - CPDF_Dictionary* pAnnotDict = - CPDFAnnotContextFromFPDFAnnotation(annot)->GetAnnotDict(); - return pAnnotDict ? pAnnotDict->GetIntegerFor("F") : FPDF_ANNOT_FLAG_NONE; -} - -FPDF_EXPORT FPDF_BOOL FPDF_CALLCONV FPDFAnnot_SetFlags(FPDF_ANNOTATION annot, - int flags) { - if (!annot) - return false; - - CPDF_Dictionary* pAnnotDict = - CPDFAnnotContextFromFPDFAnnotation(annot)->GetAnnotDict(); - if (!pAnnotDict) - return false; - - pAnnotDict->SetNewFor("F", flags); - return true; -} - -FPDF_EXPORT int FPDF_CALLCONV -FPDFAnnot_GetFormFieldFlags(FPDF_PAGE page, FPDF_ANNOTATION annot) { - CPDF_Page* pPage = CPDFPageFromFPDFPage(page); - if (!pPage || !annot) - return FPDF_FORMFLAG_NONE; - - CPDF_Dictionary* pAnnotDict = - CPDFAnnotContextFromFPDFAnnotation(annot)->GetAnnotDict(); - if (!pAnnotDict) - return FPDF_FORMFLAG_NONE; - - CPDF_InterForm interform(pPage->m_pDocument.Get()); - CPDF_FormField* pFormField = interform.GetFieldByDict(pAnnotDict); - return pFormField ? pFormField->GetFieldFlags() : FPDF_FORMFLAG_NONE; -} - -FPDF_EXPORT FPDF_ANNOTATION FPDF_CALLCONV -FPDFAnnot_GetFormFieldAtPoint(FPDF_FORMHANDLE hHandle, - FPDF_PAGE page, - double page_x, - double page_y) { - CPDF_Page* pPage = CPDFPageFromFPDFPage(page); - if (!hHandle || !pPage) - return nullptr; - - CPDF_InterForm interform(pPage->m_pDocument.Get()); - int annot_index = -1; - CPDF_FormControl* pFormCtrl = interform.GetControlAtPoint( - pPage, CFX_PointF(static_cast(page_x), static_cast(page_y)), - &annot_index); - if (!pFormCtrl || annot_index == -1) - return nullptr; - return FPDFPage_GetAnnot(page, annot_index); -} diff --git a/fpdfsdk/fpdfannot_embeddertest.cpp b/fpdfsdk/fpdfannot_embeddertest.cpp deleted file mode 100644 index 552398c41f..0000000000 --- a/fpdfsdk/fpdfannot_embeddertest.cpp +++ /dev/null @@ -1,1457 +0,0 @@ -// Copyright 2017 PDFium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#include -#include -#include -#include - -#include "core/fxcrt/fx_system.h" -#include "public/cpp/fpdf_deleters.h" -#include "public/fpdf_annot.h" -#include "public/fpdf_edit.h" -#include "public/fpdfview.h" -#include "testing/embedder_test.h" -#include "testing/gmock/include/gmock/gmock-matchers.h" -#include "testing/gtest/include/gtest/gtest.h" - -static constexpr char kContentsKey[] = "Contents"; - -class FPDFAnnotEmbeddertest : public EmbedderTest {}; - -std::wstring BufferToWString(const std::vector& buf) { - return GetPlatformWString(reinterpret_cast(buf.data())); -} - -std::string BufferToString(const std::vector& buf) { - return GetPlatformString(reinterpret_cast(buf.data())); -} - -TEST_F(FPDFAnnotEmbeddertest, RenderAnnotWithOnlyRolloverAP) { - // Open a file with one annotation and load its first page. - ASSERT_TRUE(OpenDocument("annotation_highlight_rollover_ap.pdf")); - FPDF_PAGE page = LoadPage(0); - ASSERT_TRUE(page); - - // This annotation has a malformed appearance stream, which does not have its - // normal appearance defined, only its rollover appearance. In this case, its - // normal appearance should be generated, allowing the highlight annotation to - // still display. - std::unique_ptr bitmap = - RenderLoadedPageWithFlags(page, FPDF_ANNOT); - CompareBitmap(bitmap.get(), 612, 792, "dc98f06da047bd8aabfa99562d2cbd1e"); - - UnloadPage(page); -} - -TEST_F(FPDFAnnotEmbeddertest, RenderMultilineMarkupAnnotWithoutAP) { - const char md5_hash[] = "76512832d88017668d9acc7aacd13dae"; - // Open a file with multiline markup annotations. - ASSERT_TRUE(OpenDocument("annotation_markup_multiline_no_ap.pdf")); - FPDF_PAGE page = LoadPage(0); - ASSERT_TRUE(page); - - std::unique_ptr bitmap = - RenderLoadedPageWithFlags(page, FPDF_ANNOT); - CompareBitmap(bitmap.get(), 595, 842, md5_hash); - - UnloadPage(page); -} - -TEST_F(FPDFAnnotEmbeddertest, ExtractHighlightLongContent) { - // Open a file with one annotation and load its first page. - ASSERT_TRUE(OpenDocument("annotation_highlight_long_content.pdf")); - // TODO(thestig): This test should use LoadPage() and UnloadPage(), but one of - // the FORM API calls in LoadPage() makes this test fail. So use - // FPDF_LoadPage() and FPDF_ClosePage() for now. - FPDF_PAGE page = FPDF_LoadPage(document(), 0); - ASSERT_TRUE(page); - - // Check that there is a total of 1 annotation on its first page. - EXPECT_EQ(1, FPDFPage_GetAnnotCount(page)); - - // Check that the annotation is of type "highlight". - { - std::unique_ptr annot( - FPDFPage_GetAnnot(page, 0)); - ASSERT_TRUE(annot); - EXPECT_EQ(FPDF_ANNOT_HIGHLIGHT, FPDFAnnot_GetSubtype(annot.get())); - - // Check that the annotation color is yellow. - unsigned int R; - unsigned int G; - unsigned int B; - unsigned int A; - ASSERT_TRUE(FPDFAnnot_GetColor(annot.get(), FPDFANNOT_COLORTYPE_Color, &R, - &G, &B, &A)); - EXPECT_EQ(255u, R); - EXPECT_EQ(255u, G); - EXPECT_EQ(0u, B); - EXPECT_EQ(255u, A); - - // Check that the author is correct. - static constexpr char kAuthorKey[] = "T"; - EXPECT_EQ(FPDF_OBJECT_STRING, - FPDFAnnot_GetValueType(annot.get(), kAuthorKey)); - unsigned long len = - FPDFAnnot_GetStringValue(annot.get(), kAuthorKey, nullptr, 0); - std::vector buf(len); - EXPECT_EQ(28u, FPDFAnnot_GetStringValue(annot.get(), kAuthorKey, buf.data(), - len)); - EXPECT_STREQ(L"Jae Hyun Park", BufferToWString(buf).c_str()); - - // Check that the content is correct. - EXPECT_EQ(FPDF_OBJECT_STRING, - FPDFAnnot_GetValueType(annot.get(), kContentsKey)); - len = FPDFAnnot_GetStringValue(annot.get(), kContentsKey, nullptr, 0); - buf.clear(); - buf.resize(len); - EXPECT_EQ(2690u, FPDFAnnot_GetStringValue(annot.get(), kContentsKey, - buf.data(), len)); - const wchar_t contents[] = - L"This is a note for that highlight annotation. Very long highlight " - "annotation. Long long long Long long longLong long longLong long " - "longLong long longLong long longLong long longLong long longLong long " - "longLong long longLong long longLong long longLong long longLong long " - "longLong long longLong long longLong long longLong long longLong long " - "longLong long longLong long longLong long longLong long longLong long " - "longLong long longLong long longLong long longLong long longLong long " - "longLong long longLong long longLong long longLong long longLong long " - "longLong long longLong long longLong long longLong long longLong long " - "longLong long longLong long longLong long longLong long longLong long " - "longLong long longLong long longLong long longLong long longLong long " - "longLong long longLong long longLong long longLong long longLong long " - "longLong long longLong long longLong long longLong long longLong long " - "longLong long longLong long longLong long longLong long longLong long " - "longLong long longLong long longLong long longLong long longLong long " - "longLong long longLong long longLong long longLong long longLong long " - "longLong long longLong long longLong long longLong long longLong long " - "longLong long longLong long longLong long longLong long longLong long " - "longLong long longLong long longLong long longLong long longLong long " - "longLong long long. END"; - EXPECT_STREQ(contents, BufferToWString(buf).c_str()); - - // Check that the quadpoints are correct. - FS_QUADPOINTSF quadpoints; - ASSERT_TRUE(FPDFAnnot_GetAttachmentPoints(annot.get(), &quadpoints)); - EXPECT_EQ(115.802643f, quadpoints.x1); - EXPECT_EQ(718.913940f, quadpoints.y1); - EXPECT_EQ(157.211182f, quadpoints.x4); - EXPECT_EQ(706.264465f, quadpoints.y4); - } - FPDF_ClosePage(page); -} - -TEST_F(FPDFAnnotEmbeddertest, ExtractInkMultiple) { - // Open a file with three annotations and load its first page. - ASSERT_TRUE(OpenDocument("annotation_ink_multiple.pdf")); - // TODO(thestig): This test should use LoadPage() and UnloadPage(), but one of - // the FORM API calls in LoadPage() makes this test fail. So use - // FPDF_LoadPage() and FPDF_ClosePage() for now. - FPDF_PAGE page = FPDF_LoadPage(document(), 0); - ASSERT_TRUE(page); - - // Check that there is a total of 3 annotation on its first page. - EXPECT_EQ(3, FPDFPage_GetAnnotCount(page)); - - { - // Check that the third annotation is of type "ink". - std::unique_ptr annot( - FPDFPage_GetAnnot(page, 2)); - ASSERT_TRUE(annot); - EXPECT_EQ(FPDF_ANNOT_INK, FPDFAnnot_GetSubtype(annot.get())); - - // Check that the annotation color is blue with opacity. - unsigned int R; - unsigned int G; - unsigned int B; - unsigned int A; - ASSERT_TRUE(FPDFAnnot_GetColor(annot.get(), FPDFANNOT_COLORTYPE_Color, &R, - &G, &B, &A)); - EXPECT_EQ(0u, R); - EXPECT_EQ(0u, G); - EXPECT_EQ(255u, B); - EXPECT_EQ(76u, A); - - // Check that there is no content. - EXPECT_EQ(2u, - FPDFAnnot_GetStringValue(annot.get(), kContentsKey, nullptr, 0)); - - // Check that the rectange coordinates are correct. - // Note that upon rendering, the rectangle coordinates will be adjusted. - FS_RECTF rect; - ASSERT_TRUE(FPDFAnnot_GetRect(annot.get(), &rect)); - EXPECT_EQ(351.820404f, rect.left); - EXPECT_EQ(583.830688f, rect.bottom); - EXPECT_EQ(475.336090f, rect.right); - EXPECT_EQ(681.535034f, rect.top); - } - FPDF_ClosePage(page); -} - -TEST_F(FPDFAnnotEmbeddertest, AddIllegalSubtypeAnnotation) { - // Open a file with one annotation and load its first page. - ASSERT_TRUE(OpenDocument("annotation_highlight_long_content.pdf")); - FPDF_PAGE page = LoadPage(0); - ASSERT_TRUE(page); - - // Add an annotation with an illegal subtype. - ASSERT_FALSE(FPDFPage_CreateAnnot(page, -1)); - - UnloadPage(page); -} - -TEST_F(FPDFAnnotEmbeddertest, AddFirstTextAnnotation) { - // Open a file with no annotation and load its first page. - ASSERT_TRUE(OpenDocument("hello_world.pdf")); - FPDF_PAGE page = LoadPage(0); - ASSERT_TRUE(page); - EXPECT_EQ(0, FPDFPage_GetAnnotCount(page)); - - { - // Add a text annotation to the page. - std::unique_ptr annot( - FPDFPage_CreateAnnot(page, FPDF_ANNOT_TEXT)); - ASSERT_TRUE(annot); - - // Check that there is now 1 annotations on this page. - EXPECT_EQ(1, FPDFPage_GetAnnotCount(page)); - - // Check that the subtype of the annotation is correct. - EXPECT_EQ(FPDF_ANNOT_TEXT, FPDFAnnot_GetSubtype(annot.get())); - } - - { - std::unique_ptr annot( - FPDFPage_GetAnnot(page, 0)); - ASSERT_TRUE(annot); - EXPECT_EQ(FPDF_ANNOT_TEXT, FPDFAnnot_GetSubtype(annot.get())); - - // Set the color of the annotation. - ASSERT_TRUE(FPDFAnnot_SetColor(annot.get(), FPDFANNOT_COLORTYPE_Color, 51, - 102, 153, 204)); - // Check that the color has been set correctly. - unsigned int R; - unsigned int G; - unsigned int B; - unsigned int A; - ASSERT_TRUE(FPDFAnnot_GetColor(annot.get(), FPDFANNOT_COLORTYPE_Color, &R, - &G, &B, &A)); - EXPECT_EQ(51u, R); - EXPECT_EQ(102u, G); - EXPECT_EQ(153u, B); - EXPECT_EQ(204u, A); - - // Change the color of the annotation. - ASSERT_TRUE(FPDFAnnot_SetColor(annot.get(), FPDFANNOT_COLORTYPE_Color, 204, - 153, 102, 51)); - // Check that the color has been set correctly. - ASSERT_TRUE(FPDFAnnot_GetColor(annot.get(), FPDFANNOT_COLORTYPE_Color, &R, - &G, &B, &A)); - EXPECT_EQ(204u, R); - EXPECT_EQ(153u, G); - EXPECT_EQ(102u, B); - EXPECT_EQ(51u, A); - - // Set the annotation rectangle. - FS_RECTF rect; - ASSERT_TRUE(FPDFAnnot_GetRect(annot.get(), &rect)); - EXPECT_EQ(0.f, rect.left); - EXPECT_EQ(0.f, rect.right); - rect.left = 35; - rect.bottom = 150; - rect.right = 53; - rect.top = 165; - ASSERT_TRUE(FPDFAnnot_SetRect(annot.get(), &rect)); - // Check that the annotation rectangle has been set correctly. - ASSERT_TRUE(FPDFAnnot_GetRect(annot.get(), &rect)); - EXPECT_EQ(35.f, rect.left); - EXPECT_EQ(150.f, rect.bottom); - EXPECT_EQ(53.f, rect.right); - EXPECT_EQ(165.f, rect.top); - - // Set the content of the annotation. - static constexpr wchar_t kContents[] = - L"Hello! This is a customized content."; - std::unique_ptr text = - GetFPDFWideString(kContents); - ASSERT_TRUE( - FPDFAnnot_SetStringValue(annot.get(), kContentsKey, text.get())); - // Check that the content has been set correctly. - unsigned long len = - FPDFAnnot_GetStringValue(annot.get(), kContentsKey, nullptr, 0); - std::vector buf(len); - EXPECT_EQ(74u, FPDFAnnot_GetStringValue(annot.get(), kContentsKey, - buf.data(), len)); - EXPECT_STREQ(kContents, BufferToWString(buf).c_str()); - } - UnloadPage(page); -} - -TEST_F(FPDFAnnotEmbeddertest, AddAndSaveUnderlineAnnotation) { - // Open a file with one annotation and load its first page. - ASSERT_TRUE(OpenDocument("annotation_highlight_long_content.pdf")); - FPDF_PAGE page = LoadPage(0); - ASSERT_TRUE(page); - - // Check that there is a total of one annotation on its first page, and verify - // its quadpoints. - EXPECT_EQ(1, FPDFPage_GetAnnotCount(page)); - FS_QUADPOINTSF quadpoints; - { - std::unique_ptr annot( - FPDFPage_GetAnnot(page, 0)); - ASSERT_TRUE(annot); - ASSERT_TRUE(FPDFAnnot_GetAttachmentPoints(annot.get(), &quadpoints)); - EXPECT_EQ(115.802643f, quadpoints.x1); - EXPECT_EQ(718.913940f, quadpoints.y1); - EXPECT_EQ(157.211182f, quadpoints.x4); - EXPECT_EQ(706.264465f, quadpoints.y4); - } - - // Add an underline annotation to the page and set its quadpoints. - { - std::unique_ptr annot( - FPDFPage_CreateAnnot(page, FPDF_ANNOT_UNDERLINE)); - ASSERT_TRUE(annot); - quadpoints.x1 = 140.802643f; - quadpoints.x3 = 140.802643f; - ASSERT_TRUE(FPDFAnnot_SetAttachmentPoints(annot.get(), &quadpoints)); - } - - // Save the document, closing the page and document. - EXPECT_TRUE(FPDF_SaveAsCopy(document(), this, 0)); - UnloadPage(page); - - // Open the saved document. - const char md5[] = "dba153419f67b7c0c0e3d22d3e8910d5"; - - OpenSavedDocument(); - page = LoadSavedPage(0); - VerifySavedRendering(page, 612, 792, md5); - - // Check that the saved document has 2 annotations on the first page - EXPECT_EQ(2, FPDFPage_GetAnnotCount(page)); - - { - // Check that the second annotation is an underline annotation and verify - // its quadpoints. - std::unique_ptr new_annot( - FPDFPage_GetAnnot(page, 1)); - ASSERT_TRUE(new_annot); - EXPECT_EQ(FPDF_ANNOT_UNDERLINE, FPDFAnnot_GetSubtype(new_annot.get())); - FS_QUADPOINTSF new_quadpoints; - ASSERT_TRUE( - FPDFAnnot_GetAttachmentPoints(new_annot.get(), &new_quadpoints)); - EXPECT_NEAR(quadpoints.x1, new_quadpoints.x1, 0.001f); - EXPECT_NEAR(quadpoints.y1, new_quadpoints.y1, 0.001f); - EXPECT_NEAR(quadpoints.x4, new_quadpoints.x4, 0.001f); - EXPECT_NEAR(quadpoints.y4, new_quadpoints.y4, 0.001f); - } - - CloseSavedPage(page); - CloseSavedDocument(); -} - -TEST_F(FPDFAnnotEmbeddertest, ModifyRectQuadpointsWithAP) { -#if _FX_PLATFORM_ == _FX_PLATFORM_APPLE_ - const char md5_original[] = "63af8432fab95a67cdebb7cd0e514941"; - const char md5_modified_highlight[] = "aec26075011349dec9bace891856b5f2"; - const char md5_modified_square[] = "057f57a32be95975775e5ec513fdcb56"; -#elif _FX_PLATFORM_ == _FX_PLATFORM_WINDOWS_ - const char md5_original[] = "0e27376094f11490f74c65f3dc3a42c5"; - const char md5_modified_highlight[] = "66f3caef3a7d488a4fa1ad37fc06310e"; - const char md5_modified_square[] = "a456dad0bc6801ee2d6408a4394af563"; -#else - const char md5_original[] = "0e27376094f11490f74c65f3dc3a42c5"; - const char md5_modified_highlight[] = "66f3caef3a7d488a4fa1ad37fc06310e"; - const char md5_modified_square[] = "a456dad0bc6801ee2d6408a4394af563"; -#endif - - // Open a file with four annotations and load its first page. - ASSERT_TRUE(OpenDocument("annotation_highlight_square_with_ap.pdf")); - FPDF_PAGE page = LoadPage(0); - ASSERT_TRUE(page); - EXPECT_EQ(4, FPDFPage_GetAnnotCount(page)); - - // Check that the original file renders correctly. - { - std::unique_ptr bitmap = - RenderLoadedPageWithFlags(page, FPDF_ANNOT); - CompareBitmap(bitmap.get(), 612, 792, md5_original); - } - - FS_RECTF rect; - FS_RECTF new_rect; - - // Retrieve the highlight annotation which has its AP stream already defined. - { - std::unique_ptr annot( - FPDFPage_GetAnnot(page, 0)); - ASSERT_TRUE(annot); - EXPECT_EQ(FPDF_ANNOT_HIGHLIGHT, FPDFAnnot_GetSubtype(annot.get())); - - // Check that color cannot be set when an AP stream is defined already. - EXPECT_FALSE(FPDFAnnot_SetColor(annot.get(), FPDFANNOT_COLORTYPE_Color, 51, - 102, 153, 204)); - - // Verify its attachment points. - FS_QUADPOINTSF quadpoints; - ASSERT_TRUE(FPDFAnnot_GetAttachmentPoints(annot.get(), &quadpoints)); - EXPECT_NEAR(72.0000f, quadpoints.x1, 0.001f); - EXPECT_NEAR(720.792f, quadpoints.y1, 0.001f); - EXPECT_NEAR(132.055f, quadpoints.x4, 0.001f); - EXPECT_NEAR(704.796f, quadpoints.y4, 0.001f); - - // Check that updating the attachment points would succeed. - quadpoints.x1 -= 50.f; - quadpoints.x2 -= 50.f; - quadpoints.x3 -= 50.f; - quadpoints.x4 -= 50.f; - ASSERT_TRUE(FPDFAnnot_SetAttachmentPoints(annot.get(), &quadpoints)); - FS_QUADPOINTSF new_quadpoints; - ASSERT_TRUE(FPDFAnnot_GetAttachmentPoints(annot.get(), &new_quadpoints)); - EXPECT_EQ(quadpoints.x1, new_quadpoints.x1); - EXPECT_EQ(quadpoints.y1, new_quadpoints.y1); - EXPECT_EQ(quadpoints.x4, new_quadpoints.x4); - EXPECT_EQ(quadpoints.y4, new_quadpoints.y4); - - // Check that updating quadpoints does not change the annotation's position. - { - std::unique_ptr bitmap = - RenderLoadedPageWithFlags(page, FPDF_ANNOT); - CompareBitmap(bitmap.get(), 612, 792, md5_original); - } - - // Verify its annotation rectangle. - ASSERT_TRUE(FPDFAnnot_GetRect(annot.get(), &rect)); - EXPECT_NEAR(67.7299f, rect.left, 0.001f); - EXPECT_NEAR(704.296f, rect.bottom, 0.001f); - EXPECT_NEAR(136.325f, rect.right, 0.001f); - EXPECT_NEAR(721.292f, rect.top, 0.001f); - - // Check that updating the rectangle would succeed. - rect.left -= 60.f; - rect.right -= 60.f; - ASSERT_TRUE(FPDFAnnot_SetRect(annot.get(), &rect)); - ASSERT_TRUE(FPDFAnnot_GetRect(annot.get(), &new_rect)); - EXPECT_EQ(rect.right, new_rect.right); - } - - // Check that updating the rectangle changes the annotation's position. - { - std::unique_ptr bitmap = - RenderLoadedPageWithFlags(page, FPDF_ANNOT); - CompareBitmap(bitmap.get(), 612, 792, md5_modified_highlight); - } - - { - // Retrieve the square annotation which has its AP stream already defined. - std::unique_ptr annot( - FPDFPage_GetAnnot(page, 2)); - ASSERT_TRUE(annot); - EXPECT_EQ(FPDF_ANNOT_SQUARE, FPDFAnnot_GetSubtype(annot.get())); - - // Check that updating the rectangle would succeed. - ASSERT_TRUE(FPDFAnnot_GetRect(annot.get(), &rect)); - rect.left += 70.f; - rect.right += 70.f; - ASSERT_TRUE(FPDFAnnot_SetRect(annot.get(), &rect)); - ASSERT_TRUE(FPDFAnnot_GetRect(annot.get(), &new_rect)); - EXPECT_EQ(rect.right, new_rect.right); - - // Check that updating the rectangle changes the square annotation's - // position. - std::unique_ptr bitmap = - RenderLoadedPageWithFlags(page, FPDF_ANNOT); - CompareBitmap(bitmap.get(), 612, 792, md5_modified_square); - } - - UnloadPage(page); -} - -TEST_F(FPDFAnnotEmbeddertest, CountAttachmentPoints) { - // Open a file with multiline markup annotations. - ASSERT_TRUE(OpenDocument("annotation_markup_multiline_no_ap.pdf")); - FPDF_PAGE page = LoadPage(0); - ASSERT_TRUE(page); - { - std::unique_ptr annot( - FPDFPage_GetAnnot(page, 0)); - ASSERT_TRUE(annot); - - // This is a three line annotation. - EXPECT_EQ(3u, FPDFAnnot_CountAttachmentPoints(annot.get())); - } - UnloadPage(page); - - // null annotation should return 0 - EXPECT_EQ(0u, FPDFAnnot_CountAttachmentPoints(nullptr)); -} - -TEST_F(FPDFAnnotEmbeddertest, RemoveAnnotation) { - // Open a file with 3 annotations on its first page. - ASSERT_TRUE(OpenDocument("annotation_ink_multiple.pdf")); - // TODO(thestig): This test should use LoadPage() and UnloadPage(), but one of - // the FORM API calls in LoadPage() makes this test fail. So use - // FPDF_LoadPage() and FPDF_ClosePage() for now. - FPDF_PAGE page = FPDF_LoadPage(document(), 0); - ASSERT_TRUE(page); - EXPECT_EQ(3, FPDFPage_GetAnnotCount(page)); - - FS_RECTF rect; - - // Check that the annotations have the expected rectangle coordinates. - { - std::unique_ptr annot( - FPDFPage_GetAnnot(page, 0)); - ASSERT_TRUE(FPDFAnnot_GetRect(annot.get(), &rect)); - EXPECT_NEAR(86.1971f, rect.left, 0.001f); - } - - { - std::unique_ptr annot( - FPDFPage_GetAnnot(page, 1)); - ASSERT_TRUE(FPDFAnnot_GetRect(annot.get(), &rect)); - EXPECT_NEAR(149.8127f, rect.left, 0.001f); - } - - { - std::unique_ptr annot( - FPDFPage_GetAnnot(page, 2)); - ASSERT_TRUE(FPDFAnnot_GetRect(annot.get(), &rect)); - EXPECT_NEAR(351.8204f, rect.left, 0.001f); - } - - // Check that nothing happens when attempting to remove an annotation with an - // out-of-bound index. - EXPECT_FALSE(FPDFPage_RemoveAnnot(page, 4)); - EXPECT_FALSE(FPDFPage_RemoveAnnot(page, -1)); - EXPECT_EQ(3, FPDFPage_GetAnnotCount(page)); - - // Remove the second annotation. - EXPECT_TRUE(FPDFPage_RemoveAnnot(page, 1)); - EXPECT_EQ(2, FPDFPage_GetAnnotCount(page)); - EXPECT_FALSE(FPDFPage_GetAnnot(page, 2)); - - // Save the document, closing the page and document. - EXPECT_TRUE(FPDF_SaveAsCopy(document(), this, 0)); - FPDF_ClosePage(page); - - // TODO(npm): VerifySavedRendering changes annot rect dimensions by 1?? - // Open the saved document. - std::string new_file = GetString(); - FPDF_FILEACCESS file_access; - memset(&file_access, 0, sizeof(file_access)); - file_access.m_FileLen = new_file.size(); - file_access.m_GetBlock = GetBlockFromString; - file_access.m_Param = &new_file; - FPDF_DOCUMENT new_doc = FPDF_LoadCustomDocument(&file_access, nullptr); - ASSERT_TRUE(new_doc); - FPDF_PAGE new_page = FPDF_LoadPage(new_doc, 0); - ASSERT_TRUE(new_page); - - // Check that the saved document has 2 annotations on the first page. - EXPECT_EQ(2, FPDFPage_GetAnnotCount(new_page)); - - // Check that the remaining 2 annotations are the original 1st and 3rd ones - // by verifying their rectangle coordinates. - { - std::unique_ptr annot( - FPDFPage_GetAnnot(new_page, 0)); - ASSERT_TRUE(FPDFAnnot_GetRect(annot.get(), &rect)); - EXPECT_NEAR(86.1971f, rect.left, 0.001f); - } - - { - std::unique_ptr annot( - FPDFPage_GetAnnot(new_page, 1)); - ASSERT_TRUE(FPDFAnnot_GetRect(annot.get(), &rect)); - EXPECT_NEAR(351.8204f, rect.left, 0.001f); - } - FPDF_ClosePage(new_page); - FPDF_CloseDocument(new_doc); -} - -TEST_F(FPDFAnnotEmbeddertest, AddAndModifyPath) { -#if _FX_PLATFORM_ == _FX_PLATFORM_APPLE_ - const char md5_original[] = "c35408717759562d1f8bf33d317483d2"; - const char md5_modified_path[] = "873b92ea83ccf006e58415d866ce145b"; - const char md5_two_paths[] = "6f1f1c91f50240e9cc9d7c87c48b93a7"; - const char md5_new_annot[] = "078bf58f939645ac305854f31ee9a828"; -#else - const char md5_original[] = "964f89bbe8911e540a465cf1a64b7f7e"; - const char md5_modified_path[] = "5a4a6091cff648a4ece3ce7e245e3e38"; - const char md5_two_paths[] = "d6e4072a4415cfc6ec17201fb6be0ee0"; - const char md5_new_annot[] = "fc338b97bf66a656916c6198697a8a28"; -#endif - - // Open a file with two annotations and load its first page. - ASSERT_TRUE(OpenDocument("annotation_stamp_with_ap.pdf")); - FPDF_PAGE page = LoadPage(0); - ASSERT_TRUE(page); - EXPECT_EQ(2, FPDFPage_GetAnnotCount(page)); - - // Check that the page renders correctly. - { - std::unique_ptr bitmap = - RenderLoadedPageWithFlags(page, FPDF_ANNOT); - CompareBitmap(bitmap.get(), 595, 842, md5_original); - } - - { - // Retrieve the stamp annotation which has its AP stream already defined. - std::unique_ptr annot( - FPDFPage_GetAnnot(page, 0)); - ASSERT_TRUE(annot); - - // Check that this annotation has one path object and retrieve it. - EXPECT_EQ(1, FPDFAnnot_GetObjectCount(annot.get())); - ASSERT_EQ(32, FPDFPage_CountObjects(page)); - FPDF_PAGEOBJECT path = FPDFAnnot_GetObject(annot.get(), 1); - EXPECT_FALSE(path); - path = FPDFAnnot_GetObject(annot.get(), 0); - EXPECT_EQ(FPDF_PAGEOBJ_PATH, FPDFPageObj_GetType(path)); - EXPECT_TRUE(path); - - // Modify the color of the path object. - EXPECT_TRUE(FPDFPath_SetStrokeColor(path, 0, 0, 0, 255)); - EXPECT_TRUE(FPDFAnnot_UpdateObject(annot.get(), path)); - - // Check that the page with the modified annotation renders correctly. - { - std::unique_ptr bitmap = - RenderLoadedPageWithFlags(page, FPDF_ANNOT); - CompareBitmap(bitmap.get(), 595, 842, md5_modified_path); - } - - // Add a second path object to the same annotation. - FPDF_PAGEOBJECT dot = FPDFPageObj_CreateNewPath(7, 84); - EXPECT_TRUE(FPDFPath_BezierTo(dot, 9, 86, 10, 87, 11, 88)); - EXPECT_TRUE(FPDFPath_SetStrokeColor(dot, 255, 0, 0, 100)); - EXPECT_TRUE(FPDFPath_SetStrokeWidth(dot, 14)); - EXPECT_TRUE(FPDFPath_SetDrawMode(dot, 0, 1)); - EXPECT_TRUE(FPDFAnnot_AppendObject(annot.get(), dot)); - EXPECT_EQ(2, FPDFAnnot_GetObjectCount(annot.get())); - - // The object is in the annontation, not in the page, so the page object - // array should not change. - ASSERT_EQ(32, FPDFPage_CountObjects(page)); - - // Check that the page with an annotation with two paths renders correctly. - { - std::unique_ptr bitmap = - RenderLoadedPageWithFlags(page, FPDF_ANNOT); - CompareBitmap(bitmap.get(), 595, 842, md5_two_paths); - } - - // Delete the newly added path object. - EXPECT_TRUE(FPDFAnnot_RemoveObject(annot.get(), 1)); - EXPECT_EQ(1, FPDFAnnot_GetObjectCount(annot.get())); - ASSERT_EQ(32, FPDFPage_CountObjects(page)); - } - - // Check that the page renders the same as before. - { - std::unique_ptr bitmap = - RenderLoadedPageWithFlags(page, FPDF_ANNOT); - CompareBitmap(bitmap.get(), 595, 842, md5_modified_path); - } - - FS_RECTF rect; - - { - // Create another stamp annotation and set its annotation rectangle. - std::unique_ptr annot( - FPDFPage_CreateAnnot(page, FPDF_ANNOT_STAMP)); - ASSERT_TRUE(annot); - rect.left = 200.f; - rect.bottom = 400.f; - rect.right = 500.f; - rect.top = 600.f; - EXPECT_TRUE(FPDFAnnot_SetRect(annot.get(), &rect)); - - // Add a new path to the annotation. - FPDF_PAGEOBJECT check = FPDFPageObj_CreateNewPath(200, 500); - EXPECT_TRUE(FPDFPath_LineTo(check, 300, 400)); - EXPECT_TRUE(FPDFPath_LineTo(check, 500, 600)); - EXPECT_TRUE(FPDFPath_MoveTo(check, 350, 550)); - EXPECT_TRUE(FPDFPath_LineTo(check, 450, 450)); - EXPECT_TRUE(FPDFPath_SetStrokeColor(check, 0, 255, 255, 180)); - EXPECT_TRUE(FPDFPath_SetStrokeWidth(check, 8.35f)); - EXPECT_TRUE(FPDFPath_SetDrawMode(check, 0, 1)); - EXPECT_TRUE(FPDFAnnot_AppendObject(annot.get(), check)); - EXPECT_EQ(1, FPDFAnnot_GetObjectCount(annot.get())); - - // Check that the annotation's bounding box came from its rectangle. - FS_RECTF new_rect; - ASSERT_TRUE(FPDFAnnot_GetRect(annot.get(), &new_rect)); - EXPECT_EQ(rect.left, new_rect.left); - EXPECT_EQ(rect.bottom, new_rect.bottom); - EXPECT_EQ(rect.right, new_rect.right); - EXPECT_EQ(rect.top, new_rect.top); - } - - // Save the document, closing the page and document. - EXPECT_TRUE(FPDF_SaveAsCopy(document(), this, 0)); - UnloadPage(page); - - // Open the saved document. - OpenSavedDocument(); - page = LoadSavedPage(0); - VerifySavedRendering(page, 595, 842, md5_new_annot); - - // Check that the document has a correct count of annotations and objects. - EXPECT_EQ(3, FPDFPage_GetAnnotCount(page)); - - { - std::unique_ptr annot( - FPDFPage_GetAnnot(page, 2)); - ASSERT_TRUE(annot); - EXPECT_EQ(1, FPDFAnnot_GetObjectCount(annot.get())); - - // Check that the new annotation's rectangle is as defined. - FS_RECTF new_rect; - ASSERT_TRUE(FPDFAnnot_GetRect(annot.get(), &new_rect)); - EXPECT_EQ(rect.left, new_rect.left); - EXPECT_EQ(rect.bottom, new_rect.bottom); - EXPECT_EQ(rect.right, new_rect.right); - EXPECT_EQ(rect.top, new_rect.top); - } - - CloseSavedPage(page); - CloseSavedDocument(); -} - -TEST_F(FPDFAnnotEmbeddertest, ModifyAnnotationFlags) { - // Open a file with an annotation and load its first page. - ASSERT_TRUE(OpenDocument("annotation_highlight_rollover_ap.pdf")); - FPDF_PAGE page = LoadPage(0); - ASSERT_TRUE(page); - - // Check that the page renders correctly. - { - std::unique_ptr bitmap = - RenderLoadedPageWithFlags(page, FPDF_ANNOT); - CompareBitmap(bitmap.get(), 612, 792, "dc98f06da047bd8aabfa99562d2cbd1e"); - } - - { - // Retrieve the annotation. - std::unique_ptr annot( - FPDFPage_GetAnnot(page, 0)); - ASSERT_TRUE(annot); - - // Check that the original flag values are as expected. - int flags = FPDFAnnot_GetFlags(annot.get()); - EXPECT_FALSE(flags & FPDF_ANNOT_FLAG_HIDDEN); - EXPECT_TRUE(flags & FPDF_ANNOT_FLAG_PRINT); - - // Set the HIDDEN flag. - flags |= FPDF_ANNOT_FLAG_HIDDEN; - EXPECT_TRUE(FPDFAnnot_SetFlags(annot.get(), flags)); - flags = FPDFAnnot_GetFlags(annot.get()); - EXPECT_TRUE(flags & FPDF_ANNOT_FLAG_HIDDEN); - EXPECT_TRUE(flags & FPDF_ANNOT_FLAG_PRINT); - - // Check that the page renders correctly without rendering the annotation. - { - std::unique_ptr bitmap = - RenderLoadedPageWithFlags(page, FPDF_ANNOT); - CompareBitmap(bitmap.get(), 612, 792, "1940568c9ba33bac5d0b1ee9558c76b3"); - } - - // Unset the HIDDEN flag. - EXPECT_TRUE(FPDFAnnot_SetFlags(annot.get(), FPDF_ANNOT_FLAG_NONE)); - EXPECT_FALSE(FPDFAnnot_GetFlags(annot.get())); - flags &= ~FPDF_ANNOT_FLAG_HIDDEN; - EXPECT_TRUE(FPDFAnnot_SetFlags(annot.get(), flags)); - flags = FPDFAnnot_GetFlags(annot.get()); - EXPECT_FALSE(flags & FPDF_ANNOT_FLAG_HIDDEN); - EXPECT_TRUE(flags & FPDF_ANNOT_FLAG_PRINT); - - // Check that the page renders correctly as before. - { - std::unique_ptr bitmap = - RenderLoadedPageWithFlags(page, FPDF_ANNOT); - CompareBitmap(bitmap.get(), 612, 792, "dc98f06da047bd8aabfa99562d2cbd1e"); - } - } - - UnloadPage(page); -} - -TEST_F(FPDFAnnotEmbeddertest, AddAndModifyImage) { -#if _FX_PLATFORM_ == _FX_PLATFORM_APPLE_ - const char md5_original[] = "c35408717759562d1f8bf33d317483d2"; - const char md5_new_image[] = "ff012f5697436dfcaec25b32d1333596"; - const char md5_modified_image[] = "86cf8cb2755a7a2046a543e66d9c1e61"; -#else - const char md5_original[] = "964f89bbe8911e540a465cf1a64b7f7e"; - const char md5_new_image[] = "9ea8732dc9d579f68853f16892856208"; - const char md5_modified_image[] = "74239d2a8c55c9de1dbb9cd8781895aa"; -#endif - - // Open a file with two annotations and load its first page. - ASSERT_TRUE(OpenDocument("annotation_stamp_with_ap.pdf")); - FPDF_PAGE page = LoadPage(0); - ASSERT_TRUE(page); - EXPECT_EQ(2, FPDFPage_GetAnnotCount(page)); - - // Check that the page renders correctly. - { - std::unique_ptr bitmap = - RenderLoadedPageWithFlags(page, FPDF_ANNOT); - CompareBitmap(bitmap.get(), 595, 842, md5_original); - } - - constexpr int kBitmapSize = 200; - FPDF_BITMAP image_bitmap; - - { - // Create a stamp annotation and set its annotation rectangle. - std::unique_ptr annot( - FPDFPage_CreateAnnot(page, FPDF_ANNOT_STAMP)); - ASSERT_TRUE(annot); - FS_RECTF rect; - rect.left = 200.f; - rect.bottom = 600.f; - rect.right = 400.f; - rect.top = 800.f; - EXPECT_TRUE(FPDFAnnot_SetRect(annot.get(), &rect)); - - // Add a solid-color translucent image object to the new annotation. - image_bitmap = FPDFBitmap_Create(kBitmapSize, kBitmapSize, 1); - FPDFBitmap_FillRect(image_bitmap, 0, 0, kBitmapSize, kBitmapSize, - 0xeeeecccc); - EXPECT_EQ(kBitmapSize, FPDFBitmap_GetWidth(image_bitmap)); - EXPECT_EQ(kBitmapSize, FPDFBitmap_GetHeight(image_bitmap)); - FPDF_PAGEOBJECT image_object = FPDFPageObj_NewImageObj(document()); - ASSERT_TRUE(FPDFImageObj_SetBitmap(&page, 0, image_object, image_bitmap)); - ASSERT_TRUE(FPDFImageObj_SetMatrix(image_object, kBitmapSize, 0, 0, - kBitmapSize, 0, 0)); - FPDFPageObj_Transform(image_object, 1, 0, 0, 1, 200, 600); - EXPECT_TRUE(FPDFAnnot_AppendObject(annot.get(), image_object)); - } - - // Check that the page renders correctly with the new image object. - { - std::unique_ptr bitmap = - RenderLoadedPageWithFlags(page, FPDF_ANNOT); - CompareBitmap(bitmap.get(), 595, 842, md5_new_image); - } - - { - // Retrieve the newly added stamp annotation and its image object. - std::unique_ptr annot( - FPDFPage_GetAnnot(page, 2)); - ASSERT_TRUE(annot); - EXPECT_EQ(1, FPDFAnnot_GetObjectCount(annot.get())); - FPDF_PAGEOBJECT image_object = FPDFAnnot_GetObject(annot.get(), 0); - EXPECT_EQ(FPDF_PAGEOBJ_IMAGE, FPDFPageObj_GetType(image_object)); - - // Modify the image in the new annotation. - FPDFBitmap_FillRect(image_bitmap, 0, 0, kBitmapSize, kBitmapSize, - 0xff000000); - ASSERT_TRUE(FPDFImageObj_SetBitmap(&page, 0, image_object, image_bitmap)); - EXPECT_TRUE(FPDFAnnot_UpdateObject(annot.get(), image_object)); - } - - // Save the document, closing the page and document. - EXPECT_TRUE(FPDF_SaveAsCopy(document(), this, 0)); - UnloadPage(page); - FPDFBitmap_Destroy(image_bitmap); - - // Test that the saved document renders the modified image object correctly. - VerifySavedDocument(595, 842, md5_modified_image); -} - -TEST_F(FPDFAnnotEmbeddertest, AddAndModifyText) { -#if _FX_PLATFORM_ == _FX_PLATFORM_APPLE_ - const char md5_original[] = "c35408717759562d1f8bf33d317483d2"; - const char md5_new_text[] = "e5680ed048c2cfd9a1d27212cdf41286"; - const char md5_modified_text[] = "79f5cfb0b07caaf936f65f6a7a57ce77"; -#else - const char md5_original[] = "964f89bbe8911e540a465cf1a64b7f7e"; - const char md5_new_text[] = "00b14fa2dc1c90d1b0d034e1608efef5"; - const char md5_modified_text[] = "076c8f24a09ddc0e49f7e758edead6f0"; -#endif - - // Open a file with two annotations and load its first page. - ASSERT_TRUE(OpenDocument("annotation_stamp_with_ap.pdf")); - FPDF_PAGE page = LoadPage(0); - ASSERT_TRUE(page); - EXPECT_EQ(2, FPDFPage_GetAnnotCount(page)); - - // Check that the page renders correctly. - { - std::unique_ptr bitmap = - RenderLoadedPageWithFlags(page, FPDF_ANNOT); - CompareBitmap(bitmap.get(), 595, 842, md5_original); - } - - { - // Create a stamp annotation and set its annotation rectangle. - std::unique_ptr annot( - FPDFPage_CreateAnnot(page, FPDF_ANNOT_STAMP)); - ASSERT_TRUE(annot); - FS_RECTF rect; - rect.left = 200.f; - rect.bottom = 550.f; - rect.right = 450.f; - rect.top = 650.f; - EXPECT_TRUE(FPDFAnnot_SetRect(annot.get(), &rect)); - - // Add a translucent text object to the new annotation. - FPDF_PAGEOBJECT text_object = - FPDFPageObj_NewTextObj(document(), "Arial", 12.0f); - EXPECT_TRUE(text_object); - std::unique_ptr text = - GetFPDFWideString(L"I'm a translucent text laying on other text."); - EXPECT_TRUE(FPDFText_SetText(text_object, text.get())); - EXPECT_TRUE(FPDFText_SetFillColor(text_object, 0, 0, 255, 150)); - FPDFPageObj_Transform(text_object, 1, 0, 0, 1, 200, 600); - EXPECT_TRUE(FPDFAnnot_AppendObject(annot.get(), text_object)); - } - - // Check that the page renders correctly with the new text object. - { - std::unique_ptr bitmap = - RenderLoadedPageWithFlags(page, FPDF_ANNOT); - CompareBitmap(bitmap.get(), 595, 842, md5_new_text); - } - - { - // Retrieve the newly added stamp annotation and its text object. - std::unique_ptr annot( - FPDFPage_GetAnnot(page, 2)); - ASSERT_TRUE(annot); - EXPECT_EQ(1, FPDFAnnot_GetObjectCount(annot.get())); - FPDF_PAGEOBJECT text_object = FPDFAnnot_GetObject(annot.get(), 0); - EXPECT_EQ(FPDF_PAGEOBJ_TEXT, FPDFPageObj_GetType(text_object)); - - // Modify the text in the new annotation. - std::unique_ptr new_text = - GetFPDFWideString(L"New text!"); - EXPECT_TRUE(FPDFText_SetText(text_object, new_text.get())); - EXPECT_TRUE(FPDFAnnot_UpdateObject(annot.get(), text_object)); - } - - // Check that the page renders correctly with the modified text object. - { - std::unique_ptr bitmap = - RenderLoadedPageWithFlags(page, FPDF_ANNOT); - CompareBitmap(bitmap.get(), 595, 842, md5_modified_text); - } - - // Remove the new annotation, and check that the page renders as before. - EXPECT_TRUE(FPDFPage_RemoveAnnot(page, 2)); - { - std::unique_ptr bitmap = - RenderLoadedPageWithFlags(page, FPDF_ANNOT); - CompareBitmap(bitmap.get(), 595, 842, md5_original); - } - - UnloadPage(page); -} - -TEST_F(FPDFAnnotEmbeddertest, GetSetStringValue) { - // Open a file with four annotations and load its first page. - ASSERT_TRUE(OpenDocument("annotation_stamp_with_ap.pdf")); - FPDF_PAGE page = LoadPage(0); - ASSERT_TRUE(page); - - static constexpr char kDateKey[] = "M"; - static constexpr wchar_t kNewDate[] = L"D:201706282359Z00'00'"; - - { - // Retrieve the first annotation. - std::unique_ptr annot( - FPDFPage_GetAnnot(page, 0)); - ASSERT_TRUE(annot); - - // Check that a non-existent key does not exist. - EXPECT_FALSE(FPDFAnnot_HasKey(annot.get(), "none")); - - // Check that the string value of a non-string dictionary entry is empty. - static constexpr char kApKey[] = "AP"; - EXPECT_TRUE(FPDFAnnot_HasKey(annot.get(), kApKey)); - EXPECT_EQ(FPDF_OBJECT_REFERENCE, - FPDFAnnot_GetValueType(annot.get(), kApKey)); - EXPECT_EQ(2u, FPDFAnnot_GetStringValue(annot.get(), kApKey, nullptr, 0)); - - // Check that the string value of the hash is correct. - static constexpr char kHashKey[] = "AAPL:Hash"; - EXPECT_EQ(FPDF_OBJECT_NAME, FPDFAnnot_GetValueType(annot.get(), kHashKey)); - unsigned long len = - FPDFAnnot_GetStringValue(annot.get(), kHashKey, nullptr, 0); - std::vector buf(len); - EXPECT_EQ(66u, - FPDFAnnot_GetStringValue(annot.get(), kHashKey, buf.data(), len)); - EXPECT_STREQ(L"395fbcb98d558681742f30683a62a2ad", - BufferToWString(buf).c_str()); - - // Check that the string value of the modified date is correct. - EXPECT_EQ(FPDF_OBJECT_NAME, FPDFAnnot_GetValueType(annot.get(), kHashKey)); - len = FPDFAnnot_GetStringValue(annot.get(), kDateKey, nullptr, 0); - buf.clear(); - buf.resize(len); - EXPECT_EQ(44u, - FPDFAnnot_GetStringValue(annot.get(), kDateKey, buf.data(), len)); - EXPECT_STREQ(L"D:201706071721Z00'00'", BufferToWString(buf).c_str()); - - // Update the date entry for the annotation. - std::unique_ptr text = - GetFPDFWideString(kNewDate); - EXPECT_TRUE(FPDFAnnot_SetStringValue(annot.get(), kDateKey, text.get())); - } - - // Save the document, closing the page and document. - EXPECT_TRUE(FPDF_SaveAsCopy(document(), this, 0)); - UnloadPage(page); - - // Open the saved annotation. -#if _FX_PLATFORM_ == _FX_PLATFORM_APPLE_ - const char md5[] = "4d64e61c9c0f8c60ab3cc3234bb73b1c"; -#else - const char md5[] = "c96ee1f316d7f5a1b154de9f9d467f01"; -#endif - OpenSavedDocument(); - page = LoadSavedPage(0); - VerifySavedRendering(page, 595, 842, md5); - { - std::unique_ptr new_annot( - FPDFPage_GetAnnot(page, 0)); - - // Check that the string value of the modified date is the newly-set value. - EXPECT_EQ(FPDF_OBJECT_STRING, - FPDFAnnot_GetValueType(new_annot.get(), kDateKey)); - unsigned long len = - FPDFAnnot_GetStringValue(new_annot.get(), kDateKey, nullptr, 0); - std::vector buf(len); - EXPECT_EQ(44u, FPDFAnnot_GetStringValue(new_annot.get(), kDateKey, - buf.data(), len)); - EXPECT_STREQ(kNewDate, BufferToWString(buf).c_str()); - } - - CloseSavedPage(page); - CloseSavedDocument(); -} - -TEST_F(FPDFAnnotEmbeddertest, GetSetAP) { - // Open a file with four annotations and load its first page. - ASSERT_TRUE(OpenDocument("annotation_stamp_with_ap.pdf")); - FPDF_PAGE page = LoadPage(0); - ASSERT_TRUE(page); - - { - // Retrieve the first annotation. - std::unique_ptr annot( - FPDFPage_GetAnnot(page, 0)); - ASSERT_TRUE(annot); - - // Check that the string value of an AP returns the expected length. - unsigned long normal_len = FPDFAnnot_GetAP( - annot.get(), FPDF_ANNOT_APPEARANCEMODE_NORMAL, nullptr, 0); - EXPECT_EQ(73970u, normal_len); - - // Check that the string value of an AP is not returned if the buffer is too - // small. The result buffer should be overwritten with an empty string. - std::vector buf(normal_len - 1); - // Write L"z" in the buffer to verify it's not overwritten. - wcscpy(reinterpret_cast(buf.data()), L"z"); - EXPECT_EQ(73970u, - FPDFAnnot_GetAP(annot.get(), FPDF_ANNOT_APPEARANCEMODE_NORMAL, - buf.data(), buf.size())); - std::string ap = BufferToString(buf); - EXPECT_STREQ("z", ap.c_str()); - - // Check that the string value of an AP is returned through a buffer that is - // the right size. - buf.clear(); - buf.resize(normal_len); - EXPECT_EQ(73970u, - FPDFAnnot_GetAP(annot.get(), FPDF_ANNOT_APPEARANCEMODE_NORMAL, - buf.data(), buf.size())); - ap = BufferToString(buf); - EXPECT_THAT(ap, testing::StartsWith("q Q q 7.442786 w 2 J")); - EXPECT_THAT(ap, testing::EndsWith("c 716.5381 327.7156 l S Q Q")); - - // Check that the string value of an AP is returned through a buffer that is - // larger than necessary. - buf.clear(); - buf.resize(normal_len + 1); - EXPECT_EQ(73970u, - FPDFAnnot_GetAP(annot.get(), FPDF_ANNOT_APPEARANCEMODE_NORMAL, - buf.data(), buf.size())); - ap = BufferToString(buf); - EXPECT_THAT(ap, testing::StartsWith("q Q q 7.442786 w 2 J")); - EXPECT_THAT(ap, testing::EndsWith("c 716.5381 327.7156 l S Q Q")); - - // Check that getting an AP for a mode that does not have an AP returns an - // empty string. - unsigned long rollover_len = FPDFAnnot_GetAP( - annot.get(), FPDF_ANNOT_APPEARANCEMODE_ROLLOVER, nullptr, 0); - EXPECT_EQ(2u, rollover_len); - - buf.clear(); - buf.resize(1000); - EXPECT_EQ(2u, - FPDFAnnot_GetAP(annot.get(), FPDF_ANNOT_APPEARANCEMODE_ROLLOVER, - buf.data(), buf.size())); - EXPECT_STREQ("", BufferToString(buf).c_str()); - - // Check that setting the AP for an invalid appearance mode fails. - std::unique_ptr apText = - GetFPDFWideString(L"new test ap"); - EXPECT_FALSE(FPDFAnnot_SetAP(annot.get(), -1, apText.get())); - EXPECT_FALSE(FPDFAnnot_SetAP(annot.get(), FPDF_ANNOT_APPEARANCEMODE_COUNT, - apText.get())); - EXPECT_FALSE(FPDFAnnot_SetAP( - annot.get(), FPDF_ANNOT_APPEARANCEMODE_COUNT + 1, apText.get())); - - // Set the AP correctly now. - EXPECT_TRUE(FPDFAnnot_SetAP(annot.get(), FPDF_ANNOT_APPEARANCEMODE_ROLLOVER, - apText.get())); - - // Check that the new annotation value is equal to the value we just set. - rollover_len = FPDFAnnot_GetAP( - annot.get(), FPDF_ANNOT_APPEARANCEMODE_ROLLOVER, nullptr, 0); - EXPECT_EQ(24u, rollover_len); - buf.clear(); - buf.resize(rollover_len); - EXPECT_EQ(24u, - FPDFAnnot_GetAP(annot.get(), FPDF_ANNOT_APPEARANCEMODE_ROLLOVER, - buf.data(), buf.size())); - EXPECT_STREQ(L"new test ap", BufferToWString(buf).c_str()); - - // Check that the Normal AP was not touched when the Rollover AP was set. - buf.clear(); - buf.resize(normal_len); - EXPECT_EQ(73970u, - FPDFAnnot_GetAP(annot.get(), FPDF_ANNOT_APPEARANCEMODE_NORMAL, - buf.data(), buf.size())); - ap = BufferToString(buf); - EXPECT_THAT(ap, testing::StartsWith("q Q q 7.442786 w 2 J")); - EXPECT_THAT(ap, testing::EndsWith("c 716.5381 327.7156 l S Q Q")); - } - - // Save the modified document, then reopen it. - EXPECT_TRUE(FPDF_SaveAsCopy(document(), this, 0)); - UnloadPage(page); - - OpenSavedDocument(); - page = LoadSavedPage(0); - { - std::unique_ptr new_annot( - FPDFPage_GetAnnot(page, 0)); - - // Check that the new annotation value is equal to the value we set before - // saving. - unsigned long rollover_len = FPDFAnnot_GetAP( - new_annot.get(), FPDF_ANNOT_APPEARANCEMODE_ROLLOVER, nullptr, 0); - EXPECT_EQ(24u, rollover_len); - std::vector buf(rollover_len); - EXPECT_EQ(24u, FPDFAnnot_GetAP(new_annot.get(), - FPDF_ANNOT_APPEARANCEMODE_ROLLOVER, - buf.data(), buf.size())); - EXPECT_STREQ(L"new test ap", BufferToWString(buf).c_str()); - } - - // Close saved document. - CloseSavedPage(page); - CloseSavedDocument(); -} - -TEST_F(FPDFAnnotEmbeddertest, RemoveOptionalAP) { - // Open a file with four annotations and load its first page. - ASSERT_TRUE(OpenDocument("annotation_stamp_with_ap.pdf")); - FPDF_PAGE page = LoadPage(0); - ASSERT_TRUE(page); - - { - // Retrieve the first annotation. - std::unique_ptr annot( - FPDFPage_GetAnnot(page, 0)); - ASSERT_TRUE(annot); - - // Set Down AP. Normal AP is already set. - std::unique_ptr apText = - GetFPDFWideString(L"new test ap"); - EXPECT_TRUE(FPDFAnnot_SetAP(annot.get(), FPDF_ANNOT_APPEARANCEMODE_DOWN, - apText.get())); - EXPECT_EQ(73970u, - FPDFAnnot_GetAP(annot.get(), FPDF_ANNOT_APPEARANCEMODE_NORMAL, - nullptr, 0)); - EXPECT_EQ(24u, FPDFAnnot_GetAP(annot.get(), FPDF_ANNOT_APPEARANCEMODE_DOWN, - nullptr, 0)); - - // Check that setting the Down AP to null removes the Down entry but keeps - // Normal intact. - EXPECT_TRUE( - FPDFAnnot_SetAP(annot.get(), FPDF_ANNOT_APPEARANCEMODE_DOWN, nullptr)); - EXPECT_EQ(73970u, - FPDFAnnot_GetAP(annot.get(), FPDF_ANNOT_APPEARANCEMODE_NORMAL, - nullptr, 0)); - EXPECT_EQ(2u, FPDFAnnot_GetAP(annot.get(), FPDF_ANNOT_APPEARANCEMODE_DOWN, - nullptr, 0)); - } - - UnloadPage(page); -} - -TEST_F(FPDFAnnotEmbeddertest, RemoveRequiredAP) { - // Open a file with four annotations and load its first page. - ASSERT_TRUE(OpenDocument("annotation_stamp_with_ap.pdf")); - FPDF_PAGE page = LoadPage(0); - ASSERT_TRUE(page); - - { - // Retrieve the first annotation. - std::unique_ptr annot( - FPDFPage_GetAnnot(page, 0)); - ASSERT_TRUE(annot); - - // Set Down AP. Normal AP is already set. - std::unique_ptr apText = - GetFPDFWideString(L"new test ap"); - EXPECT_TRUE(FPDFAnnot_SetAP(annot.get(), FPDF_ANNOT_APPEARANCEMODE_DOWN, - apText.get())); - EXPECT_EQ(73970u, - FPDFAnnot_GetAP(annot.get(), FPDF_ANNOT_APPEARANCEMODE_NORMAL, - nullptr, 0)); - EXPECT_EQ(24u, FPDFAnnot_GetAP(annot.get(), FPDF_ANNOT_APPEARANCEMODE_DOWN, - nullptr, 0)); - - // Check that setting the Normal AP to null removes the whole AP dictionary. - EXPECT_TRUE(FPDFAnnot_SetAP(annot.get(), FPDF_ANNOT_APPEARANCEMODE_NORMAL, - nullptr)); - EXPECT_EQ(2u, FPDFAnnot_GetAP(annot.get(), FPDF_ANNOT_APPEARANCEMODE_NORMAL, - nullptr, 0)); - EXPECT_EQ(2u, FPDFAnnot_GetAP(annot.get(), FPDF_ANNOT_APPEARANCEMODE_DOWN, - nullptr, 0)); - } - - UnloadPage(page); -} - -TEST_F(FPDFAnnotEmbeddertest, ExtractLinkedAnnotations) { - // Open a file with annotations and load its first page. - ASSERT_TRUE(OpenDocument("annotation_highlight_square_with_ap.pdf")); - FPDF_PAGE page = LoadPage(0); - ASSERT_TRUE(page); - EXPECT_EQ(-1, FPDFPage_GetAnnotIndex(page, nullptr)); - - { - // Retrieve the highlight annotation which has its popup defined. - std::unique_ptr annot( - FPDFPage_GetAnnot(page, 0)); - ASSERT_TRUE(annot); - EXPECT_EQ(FPDF_ANNOT_HIGHLIGHT, FPDFAnnot_GetSubtype(annot.get())); - EXPECT_EQ(0, FPDFPage_GetAnnotIndex(page, annot.get())); - static constexpr char kPopupKey[] = "Popup"; - ASSERT_TRUE(FPDFAnnot_HasKey(annot.get(), kPopupKey)); - ASSERT_EQ(FPDF_OBJECT_REFERENCE, - FPDFAnnot_GetValueType(annot.get(), kPopupKey)); - - // Retrieve and verify the popup of the highlight annotation. - std::unique_ptr popup( - FPDFAnnot_GetLinkedAnnot(annot.get(), kPopupKey)); - ASSERT_TRUE(popup); - EXPECT_EQ(FPDF_ANNOT_POPUP, FPDFAnnot_GetSubtype(popup.get())); - EXPECT_EQ(1, FPDFPage_GetAnnotIndex(page, popup.get())); - FS_RECTF rect; - ASSERT_TRUE(FPDFAnnot_GetRect(popup.get(), &rect)); - EXPECT_NEAR(612.0f, rect.left, 0.001f); - EXPECT_NEAR(578.792, rect.bottom, 0.001f); - - // Attempting to retrieve |annot|'s "IRT"-linked annotation would fail, - // since "IRT" is not a key in |annot|'s dictionary. - static constexpr char kIRTKey[] = "IRT"; - ASSERT_FALSE(FPDFAnnot_HasKey(annot.get(), kIRTKey)); - EXPECT_FALSE(FPDFAnnot_GetLinkedAnnot(annot.get(), kIRTKey)); - - // Attempting to retrieve |annot|'s parent dictionary as an annotation - // would fail, since its parent is not an annotation. - static constexpr char kPKey[] = "P"; - ASSERT_TRUE(FPDFAnnot_HasKey(annot.get(), kPKey)); - EXPECT_EQ(FPDF_OBJECT_REFERENCE, - FPDFAnnot_GetValueType(annot.get(), kPKey)); - EXPECT_FALSE(FPDFAnnot_GetLinkedAnnot(annot.get(), kPKey)); - } - - UnloadPage(page); -} - -TEST_F(FPDFAnnotEmbeddertest, GetFormFieldFlagsTextField) { - // Open file with form text fields. - ASSERT_TRUE(OpenDocument("text_form_multiple.pdf")); - FPDF_PAGE page = LoadPage(0); - ASSERT_TRUE(page); - - { - // Retrieve the first annotation: user-editable text field. - std::unique_ptr annot( - FPDFPage_GetAnnot(page, 0)); - ASSERT_TRUE(annot); - - // Check that the flag values are as expected. - int flags = FPDFAnnot_GetFormFieldFlags(page, annot.get()); - EXPECT_FALSE(flags & FPDF_FORMFLAG_READONLY); - } - - { - // Retrieve the second annotation: read-only text field. - std::unique_ptr annot( - FPDFPage_GetAnnot(page, 1)); - ASSERT_TRUE(annot); - - // Check that the flag values are as expected. - int flags = FPDFAnnot_GetFormFieldFlags(page, annot.get()); - EXPECT_TRUE(flags & FPDF_FORMFLAG_READONLY); - } - - UnloadPage(page); -} - -TEST_F(FPDFAnnotEmbeddertest, GetFormFieldFlagsComboBox) { - // Open file with form text fields. - ASSERT_TRUE(OpenDocument("combobox_form.pdf")); - FPDF_PAGE page = LoadPage(0); - ASSERT_TRUE(page); - - { - // Retrieve the first annotation: user-editable combobox. - std::unique_ptr annot( - FPDFPage_GetAnnot(page, 0)); - ASSERT_TRUE(annot); - - // Check that the flag values are as expected. - int flags = FPDFAnnot_GetFormFieldFlags(page, annot.get()); - EXPECT_FALSE(flags & FPDF_FORMFLAG_READONLY); - EXPECT_TRUE(flags & FPDF_FORMFLAG_CHOICE_COMBO); - EXPECT_TRUE(flags & FPDF_FORMFLAG_CHOICE_EDIT); - } - - { - // Retrieve the second annotation: regular combobox. - std::unique_ptr annot( - FPDFPage_GetAnnot(page, 1)); - ASSERT_TRUE(annot); - - // Check that the flag values are as expected. - int flags = FPDFAnnot_GetFormFieldFlags(page, annot.get()); - EXPECT_FALSE(flags & FPDF_FORMFLAG_READONLY); - EXPECT_TRUE(flags & FPDF_FORMFLAG_CHOICE_COMBO); - EXPECT_FALSE(flags & FPDF_FORMFLAG_CHOICE_EDIT); - } - - { - // Retrieve the third annotation: read-only combobox. - std::unique_ptr annot( - FPDFPage_GetAnnot(page, 2)); - ASSERT_TRUE(annot); - - // Check that the flag values are as expected. - int flags = FPDFAnnot_GetFormFieldFlags(page, annot.get()); - EXPECT_TRUE(flags & FPDF_FORMFLAG_READONLY); - EXPECT_TRUE(flags & FPDF_FORMFLAG_CHOICE_COMBO); - EXPECT_FALSE(flags & FPDF_FORMFLAG_CHOICE_EDIT); - } - - UnloadPage(page); -} - -TEST_F(FPDFAnnotEmbeddertest, GetFormAnnotNull) { - // Open file with form text fields. - EXPECT_TRUE(OpenDocument("text_form.pdf")); - FPDF_PAGE page = LoadPage(0); - ASSERT_TRUE(page); - - // Attempt to get an annotation where no annotation exists on page. - FPDF_ANNOTATION annot = - FPDFAnnot_GetFormFieldAtPoint(form_handle(), page, 0, 0); - EXPECT_FALSE(annot); - - UnloadPage(page); -} - -TEST_F(FPDFAnnotEmbeddertest, GetFormAnnotAndCheckFlagsTextField) { - // Open file with form text fields. - EXPECT_TRUE(OpenDocument("text_form_multiple.pdf")); - FPDF_PAGE page = LoadPage(0); - ASSERT_TRUE(page); - - { - // Retrieve user-editable text field annotation. - std::unique_ptr annot( - FPDFAnnot_GetFormFieldAtPoint(form_handle(), page, 105, 118)); - ASSERT_TRUE(annot); - - // Check that interactive form annotation flag values are as expected. - int flags = FPDFAnnot_GetFormFieldFlags(page, annot.get()); - EXPECT_FALSE(flags & FPDF_FORMFLAG_READONLY); - } - - { - // Retrieve read-only text field annotation. - std::unique_ptr annot( - FPDFAnnot_GetFormFieldAtPoint(form_handle(), page, 105, 202)); - ASSERT_TRUE(annot); - - // Check that interactive form annotation flag values are as expected. - int flags = FPDFAnnot_GetFormFieldFlags(page, annot.get()); - EXPECT_TRUE(flags & FPDF_FORMFLAG_READONLY); - } - - UnloadPage(page); -} - -TEST_F(FPDFAnnotEmbeddertest, GetFormAnnotAndCheckFlagsComboBox) { - // Open file with form comboboxes. - EXPECT_TRUE(OpenDocument("combobox_form.pdf")); - FPDF_PAGE page = LoadPage(0); - ASSERT_TRUE(page); - - { - // Retrieve user-editable combobox annotation. - std::unique_ptr annot( - FPDFAnnot_GetFormFieldAtPoint(form_handle(), page, 102, 363)); - ASSERT_TRUE(annot); - - // Check that interactive form annotation flag values are as expected. - int flags = FPDFAnnot_GetFormFieldFlags(page, annot.get()); - EXPECT_FALSE(flags & FPDF_FORMFLAG_READONLY); - EXPECT_TRUE(flags & FPDF_FORMFLAG_CHOICE_COMBO); - EXPECT_TRUE(flags & FPDF_FORMFLAG_CHOICE_EDIT); - } - - { - // Retrieve regular combobox annotation. - std::unique_ptr annot( - FPDFAnnot_GetFormFieldAtPoint(form_handle(), page, 102, 413)); - ASSERT_TRUE(annot); - - // Check that interactive form annotation flag values are as expected. - int flags = FPDFAnnot_GetFormFieldFlags(page, annot.get()); - EXPECT_FALSE(flags & FPDF_FORMFLAG_READONLY); - EXPECT_TRUE(flags & FPDF_FORMFLAG_CHOICE_COMBO); - EXPECT_FALSE(flags & FPDF_FORMFLAG_CHOICE_EDIT); - } - - { - // Retrieve read-only combobox annotation. - std::unique_ptr annot( - FPDFAnnot_GetFormFieldAtPoint(form_handle(), page, 102, 513)); - ASSERT_TRUE(annot); - - // Check that interactive form annotation flag values are as expected. - int flags = FPDFAnnot_GetFormFieldFlags(page, annot.get()); - EXPECT_TRUE(flags & FPDF_FORMFLAG_READONLY); - EXPECT_TRUE(flags & FPDF_FORMFLAG_CHOICE_COMBO); - EXPECT_FALSE(flags & FPDF_FORMFLAG_CHOICE_EDIT); - } - - UnloadPage(page); -} diff --git a/fpdfsdk/fpdfattachment.cpp b/fpdfsdk/fpdfattachment.cpp deleted file mode 100644 index 0bb9364834..0000000000 --- a/fpdfsdk/fpdfattachment.cpp +++ /dev/null @@ -1,275 +0,0 @@ -// Copyright 2017 PDFium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#include "public/fpdf_attachment.h" - -#include -#include - -#include "core/fdrm/crypto/fx_crypt.h" -#include "core/fpdfapi/parser/cpdf_array.h" -#include "core/fpdfapi/parser/cpdf_document.h" -#include "core/fpdfapi/parser/cpdf_name.h" -#include "core/fpdfapi/parser/cpdf_number.h" -#include "core/fpdfapi/parser/cpdf_reference.h" -#include "core/fpdfapi/parser/cpdf_string.h" -#include "core/fpdfapi/parser/fpdf_parser_decode.h" -#include "core/fpdfdoc/cpdf_filespec.h" -#include "core/fpdfdoc/cpdf_nametree.h" -#include "core/fxcrt/cfx_datetime.h" -#include "core/fxcrt/fx_extension.h" -#include "fpdfsdk/cpdfsdk_helpers.h" - -namespace { - -constexpr char kChecksumKey[] = "CheckSum"; - -CPDF_Object* CPDFObjectFromFPDFAttachment(FPDF_ATTACHMENT attachment) { - return static_cast(attachment); -} - -ByteString CFXByteStringHexDecode(const ByteString& bsHex) { - uint8_t* result = nullptr; - uint32_t size = 0; - HexDecode(bsHex.raw_str(), bsHex.GetLength(), &result, &size); - ByteString bsDecoded(result, size); - FX_Free(result); - return bsDecoded; -} - -ByteString GenerateMD5Base16(const void* contents, const unsigned long len) { - uint8_t digest[16]; - CRYPT_MD5Generate(reinterpret_cast(contents), len, digest); - char buf[32]; - for (int i = 0; i < 16; ++i) - FXSYS_IntToTwoHexChars(digest[i], &buf[i * 2]); - - return ByteString(buf, 32); -} - -} // namespace - -FPDF_EXPORT int FPDF_CALLCONV -FPDFDoc_GetAttachmentCount(FPDF_DOCUMENT document) { - CPDF_Document* pDoc = CPDFDocumentFromFPDFDocument(document); - if (!pDoc) - return 0; - - return CPDF_NameTree(pDoc, "EmbeddedFiles").GetCount(); -} - -FPDF_EXPORT FPDF_ATTACHMENT FPDF_CALLCONV -FPDFDoc_AddAttachment(FPDF_DOCUMENT document, FPDF_WIDESTRING name) { - CPDF_Document* pDoc = CPDFDocumentFromFPDFDocument(document); - WideString wsName = - WideString::FromUTF16LE(name, WideString::WStringLength(name)); - if (!pDoc || wsName.IsEmpty()) - return nullptr; - - CPDF_Dictionary* pRoot = pDoc->GetRoot(); - if (!pRoot) - return nullptr; - - // Retrieve the document's Names dictionary; create it if missing. - CPDF_Dictionary* pNames = pRoot->GetDictFor("Names"); - if (!pNames) { - pNames = pDoc->NewIndirect(); - pRoot->SetNewFor("Names", pDoc, pNames->GetObjNum()); - } - - // Create the EmbeddedFiles dictionary if missing. - if (!pNames->GetDictFor("EmbeddedFiles")) { - CPDF_Dictionary* pFiles = pDoc->NewIndirect(); - pFiles->SetNewFor("Names"); - pNames->SetNewFor("EmbeddedFiles", pDoc, - pFiles->GetObjNum()); - } - - // Set up the basic entries in the filespec dictionary. - CPDF_Dictionary* pFile = pDoc->NewIndirect(); - pFile->SetNewFor("Type", "Filespec"); - pFile->SetNewFor("UF", wsName); - pFile->SetNewFor("F", wsName); - - // Add the new attachment name and filespec into the document's EmbeddedFiles. - CPDF_NameTree nameTree(pDoc, "EmbeddedFiles"); - if (!nameTree.AddValueAndName( - pdfium::MakeUnique(pDoc, pFile->GetObjNum()), - wsName)) { - return nullptr; - } - - return pFile; -} - -FPDF_EXPORT FPDF_ATTACHMENT FPDF_CALLCONV -FPDFDoc_GetAttachment(FPDF_DOCUMENT document, int index) { - CPDF_Document* pDoc = CPDFDocumentFromFPDFDocument(document); - if (!pDoc || index < 0) - return nullptr; - - CPDF_NameTree nameTree(pDoc, "EmbeddedFiles"); - if (static_cast(index) >= nameTree.GetCount()) - return nullptr; - - WideString csName; - return nameTree.LookupValueAndName(index, &csName); -} - -FPDF_EXPORT FPDF_BOOL FPDF_CALLCONV -FPDFDoc_DeleteAttachment(FPDF_DOCUMENT document, int index) { - CPDF_Document* pDoc = CPDFDocumentFromFPDFDocument(document); - if (!pDoc || index < 0) - return false; - - CPDF_NameTree nameTree(pDoc, "EmbeddedFiles"); - if (static_cast(index) >= nameTree.GetCount()) - return false; - - return nameTree.DeleteValueAndName(index); -} - -FPDF_EXPORT unsigned long FPDF_CALLCONV -FPDFAttachment_GetName(FPDF_ATTACHMENT attachment, - void* buffer, - unsigned long buflen) { - CPDF_Object* pFile = CPDFObjectFromFPDFAttachment(attachment); - if (!pFile) - return 0; - - return Utf16EncodeMaybeCopyAndReturnLength(CPDF_FileSpec(pFile).GetFileName(), - buffer, buflen); -} - -FPDF_EXPORT FPDF_BOOL FPDF_CALLCONV -FPDFAttachment_HasKey(FPDF_ATTACHMENT attachment, FPDF_BYTESTRING key) { - CPDF_Object* pFile = CPDFObjectFromFPDFAttachment(attachment); - if (!pFile) - return 0; - - CPDF_Dictionary* pParamsDict = CPDF_FileSpec(pFile).GetParamsDict(); - return pParamsDict ? pParamsDict->KeyExist(key) : 0; -} - -FPDF_EXPORT FPDF_OBJECT_TYPE FPDF_CALLCONV -FPDFAttachment_GetValueType(FPDF_ATTACHMENT attachment, FPDF_BYTESTRING key) { - if (!FPDFAttachment_HasKey(attachment, key)) - return FPDF_OBJECT_UNKNOWN; - - CPDF_FileSpec spec(CPDFObjectFromFPDFAttachment(attachment)); - CPDF_Object* pObj = spec.GetParamsDict()->GetObjectFor(key); - return pObj ? pObj->GetType() : FPDF_OBJECT_UNKNOWN; -} - -FPDF_EXPORT FPDF_BOOL FPDF_CALLCONV -FPDFAttachment_SetStringValue(FPDF_ATTACHMENT attachment, - FPDF_BYTESTRING key, - FPDF_WIDESTRING value) { - CPDF_Object* pFile = CPDFObjectFromFPDFAttachment(attachment); - if (!pFile) - return false; - - CPDF_Dictionary* pParamsDict = CPDF_FileSpec(pFile).GetParamsDict(); - if (!pParamsDict) - return false; - - ByteString bsKey = key; - ByteString bsValue = CFXByteStringFromFPDFWideString(value); - bool bEncodedAsHex = bsKey == kChecksumKey; - if (bEncodedAsHex) - bsValue = CFXByteStringHexDecode(bsValue); - - pParamsDict->SetNewFor(bsKey, bsValue, bEncodedAsHex); - return true; -} - -FPDF_EXPORT unsigned long FPDF_CALLCONV -FPDFAttachment_GetStringValue(FPDF_ATTACHMENT attachment, - FPDF_BYTESTRING key, - void* buffer, - unsigned long buflen) { - CPDF_Object* pFile = CPDFObjectFromFPDFAttachment(attachment); - if (!pFile) - return 0; - - CPDF_Dictionary* pParamsDict = CPDF_FileSpec(pFile).GetParamsDict(); - if (!pParamsDict) - return 0; - - ByteString bsKey = key; - WideString value = pParamsDict->GetUnicodeTextFor(bsKey); - if (bsKey == kChecksumKey && !value.IsEmpty()) { - CPDF_String* stringValue = pParamsDict->GetObjectFor(bsKey)->AsString(); - if (stringValue->IsHex()) { - ByteString encoded = PDF_EncodeString(stringValue->GetString(), true); - value = CPDF_String(nullptr, encoded, false).GetUnicodeText(); - } - } - - return Utf16EncodeMaybeCopyAndReturnLength(value, buffer, buflen); -} - -FPDF_EXPORT FPDF_BOOL FPDF_CALLCONV -FPDFAttachment_SetFile(FPDF_ATTACHMENT attachment, - FPDF_DOCUMENT document, - const void* contents, - const unsigned long len) { - CPDF_Object* pFile = CPDFObjectFromFPDFAttachment(attachment); - CPDF_Document* pDoc = CPDFDocumentFromFPDFDocument(document); - if (!pFile || !pFile->IsDictionary() || !pDoc || len > INT_MAX) - return false; - - // An empty content must have a zero length. - if (!contents && len != 0) - return false; - - // Create a dictionary for the new embedded file stream. - auto pFileStreamDict = pdfium::MakeUnique(); - CPDF_Dictionary* pParamsDict = - pFileStreamDict->SetNewFor("Params"); - - // Set the size of the new file in the dictionary. - pFileStreamDict->SetNewFor("DL", static_cast(len)); - pParamsDict->SetNewFor("Size", static_cast(len)); - - // Set the creation date of the new attachment in the dictionary. - CFX_DateTime dateTime = CFX_DateTime::Now(); - pParamsDict->SetNewFor( - "CreationDate", - ByteString::Format("D:%d%02d%02d%02d%02d%02d", dateTime.GetYear(), - dateTime.GetMonth(), dateTime.GetDay(), - dateTime.GetHour(), dateTime.GetMinute(), - dateTime.GetSecond()), - false); - - // Set the checksum of the new attachment in the dictionary. - pParamsDict->SetNewFor( - kChecksumKey, CFXByteStringHexDecode(GenerateMD5Base16(contents, len)), - true); - - // Create the file stream and have the filespec dictionary link to it. - std::unique_ptr stream(FX_Alloc(uint8_t, len)); - memcpy(stream.get(), contents, len); - CPDF_Stream* pFileStream = pDoc->NewIndirect( - std::move(stream), len, std::move(pFileStreamDict)); - CPDF_Dictionary* pEFDict = - pFile->AsDictionary()->SetNewFor("EF"); - pEFDict->SetNewFor("F", pDoc, pFileStream->GetObjNum()); - return true; -} - -FPDF_EXPORT unsigned long FPDF_CALLCONV -FPDFAttachment_GetFile(FPDF_ATTACHMENT attachment, - void* buffer, - unsigned long buflen) { - CPDF_Object* pFile = CPDFObjectFromFPDFAttachment(attachment); - if (!pFile) - return 0; - - CPDF_Stream* pFileStream = CPDF_FileSpec(pFile).GetFileStream(); - if (!pFileStream) - return 0; - - return DecodeStreamMaybeCopyAndReturnLength(pFileStream, buffer, buflen); -} diff --git a/fpdfsdk/fpdfattachment_embeddertest.cpp b/fpdfsdk/fpdfattachment_embeddertest.cpp deleted file mode 100644 index dd9b5aebf3..0000000000 --- a/fpdfsdk/fpdfattachment_embeddertest.cpp +++ /dev/null @@ -1,260 +0,0 @@ -// Copyright 2017 PDFium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#include -#include -#include - -#include "public/fpdf_attachment.h" -#include "public/fpdfview.h" -#include "testing/embedder_test.h" - -static constexpr char kDateKey[] = "CreationDate"; -static constexpr char kChecksumKey[] = "CheckSum"; - -class FPDFAttachmentEmbeddertest : public EmbedderTest {}; - -TEST_F(FPDFAttachmentEmbeddertest, ExtractAttachments) { - // Open a file with two attachments. - ASSERT_TRUE(OpenDocument("embedded_attachments.pdf")); - EXPECT_EQ(2, FPDFDoc_GetAttachmentCount(document())); - - // Retrieve the first attachment. - FPDF_ATTACHMENT attachment = FPDFDoc_GetAttachment(document(), 0); - ASSERT_TRUE(attachment); - - // Check that the name of the first attachment is correct. - unsigned long len = FPDFAttachment_GetName(attachment, nullptr, 0); - std::vector buf(len); - EXPECT_EQ(12u, FPDFAttachment_GetName(attachment, buf.data(), len)); - EXPECT_STREQ(L"1.txt", - GetPlatformWString(reinterpret_cast(buf.data())) - .c_str()); - - // Check that the content of the first attachment is correct. - len = FPDFAttachment_GetFile(attachment, nullptr, 0); - buf.clear(); - buf.resize(len); - ASSERT_EQ(4u, FPDFAttachment_GetFile(attachment, buf.data(), len)); - EXPECT_EQ(std::string("test"), std::string(buf.data(), 4)); - - // Check that a non-existent key does not exist. - EXPECT_FALSE(FPDFAttachment_HasKey(attachment, "none")); - - // Check that the string value of a non-string dictionary entry is empty. - static constexpr char kSizeKey[] = "Size"; - EXPECT_EQ(FPDF_OBJECT_NUMBER, - FPDFAttachment_GetValueType(attachment, kSizeKey)); - EXPECT_EQ(2u, - FPDFAttachment_GetStringValue(attachment, kSizeKey, nullptr, 0)); - - // Check that the creation date of the first attachment is correct. - len = FPDFAttachment_GetStringValue(attachment, kDateKey, nullptr, 0); - buf.clear(); - buf.resize(len); - EXPECT_EQ(48u, FPDFAttachment_GetStringValue(attachment, kDateKey, buf.data(), - len)); - EXPECT_STREQ(L"D:20170712214438-07'00'", - GetPlatformWString(reinterpret_cast(buf.data())) - .c_str()); - - // Retrieve the second attachment. - attachment = FPDFDoc_GetAttachment(document(), 1); - ASSERT_TRUE(attachment); - - // Retrieve the second attachment file. - len = FPDFAttachment_GetFile(attachment, nullptr, 0); - buf.clear(); - buf.resize(len); - EXPECT_EQ(5869u, FPDFAttachment_GetFile(attachment, buf.data(), len)); - - // Check that the calculated checksum of the file data matches expectation. - const char kCheckSum[] = "72afcddedf554dda63c0c88e06f1ce18"; - const wchar_t kCheckSumW[] = L"<72AFCDDEDF554DDA63C0C88E06F1CE18>"; - const std::string generated_checksum = - GenerateMD5Base16(reinterpret_cast(buf.data()), len); - EXPECT_EQ(kCheckSum, generated_checksum); - - // Check that the stored checksum matches expectation. - len = FPDFAttachment_GetStringValue(attachment, kChecksumKey, nullptr, 0); - buf.clear(); - buf.resize(len); - EXPECT_EQ(70u, FPDFAttachment_GetStringValue(attachment, kChecksumKey, - buf.data(), len)); - EXPECT_EQ(kCheckSumW, - GetPlatformWString(reinterpret_cast(buf.data()))); -} - -TEST_F(FPDFAttachmentEmbeddertest, AddAttachments) { - // Open a file with two attachments. - ASSERT_TRUE(OpenDocument("embedded_attachments.pdf")); - EXPECT_EQ(2, FPDFDoc_GetAttachmentCount(document())); - - // Check that adding an attachment with an empty name would fail. - EXPECT_FALSE(FPDFDoc_AddAttachment(document(), nullptr)); - - // Add an attachment to the beginning of the embedded file list. - std::unique_ptr file_name = - GetFPDFWideString(L"0.txt"); - FPDF_ATTACHMENT attachment = - FPDFDoc_AddAttachment(document(), file_name.get()); - - // Check that writing to a file with nullptr but non-zero bytes would fail. - EXPECT_FALSE(FPDFAttachment_SetFile(attachment, document(), nullptr, 10)); - - // Set the new attachment's file. - constexpr char kContents1[] = "Hello!"; - EXPECT_TRUE(FPDFAttachment_SetFile(attachment, document(), kContents1, - strlen(kContents1))); - - // Verify the name of the new attachment (i.e. the first attachment). - attachment = FPDFDoc_GetAttachment(document(), 0); - ASSERT_TRUE(attachment); - unsigned long len = FPDFAttachment_GetName(attachment, nullptr, 0); - std::vector buf(len); - EXPECT_EQ(12u, FPDFAttachment_GetName(attachment, buf.data(), len)); - EXPECT_STREQ(L"0.txt", - GetPlatformWString(reinterpret_cast(buf.data())) - .c_str()); - - // Verify the content of the new attachment (i.e. the first attachment). - len = FPDFAttachment_GetFile(attachment, nullptr, 0); - buf.clear(); - buf.resize(len); - ASSERT_EQ(6u, FPDFAttachment_GetFile(attachment, buf.data(), len)); - EXPECT_EQ(std::string(kContents1), std::string(buf.data(), 6)); - - // Add an attachment to the end of the embedded file list and set its file. - file_name = GetFPDFWideString(L"z.txt"); - attachment = FPDFDoc_AddAttachment(document(), file_name.get()); - constexpr char kContents2[] = "World!"; - EXPECT_TRUE(FPDFAttachment_SetFile(attachment, document(), kContents2, - strlen(kContents2))); - EXPECT_EQ(4, FPDFDoc_GetAttachmentCount(document())); - - // Verify the name of the new attachment (i.e. the fourth attachment). - attachment = FPDFDoc_GetAttachment(document(), 3); - ASSERT_TRUE(attachment); - len = FPDFAttachment_GetName(attachment, nullptr, 0); - buf.clear(); - buf.resize(len); - EXPECT_EQ(12u, FPDFAttachment_GetName(attachment, buf.data(), len)); - EXPECT_STREQ(L"z.txt", - GetPlatformWString(reinterpret_cast(buf.data())) - .c_str()); - - // Verify the content of the new attachment (i.e. the fourth attachment). - len = FPDFAttachment_GetFile(attachment, nullptr, 0); - buf.clear(); - buf.resize(len); - ASSERT_EQ(6u, FPDFAttachment_GetFile(attachment, buf.data(), len)); - EXPECT_EQ(std::string(kContents2), std::string(buf.data(), 6)); -} - -TEST_F(FPDFAttachmentEmbeddertest, AddAttachmentsWithParams) { - // Open a file with two attachments. - ASSERT_TRUE(OpenDocument("embedded_attachments.pdf")); - EXPECT_EQ(2, FPDFDoc_GetAttachmentCount(document())); - - // Add an attachment to the embedded file list. - std::unique_ptr file_name = - GetFPDFWideString(L"5.txt"); - FPDF_ATTACHMENT attachment = - FPDFDoc_AddAttachment(document(), file_name.get()); - constexpr char kContents[] = "Hello World!"; - EXPECT_TRUE(FPDFAttachment_SetFile(attachment, document(), kContents, - strlen(kContents))); - - // Set the date to be an arbitrary value. - constexpr wchar_t kDateW[] = L"D:20170720161527-04'00'"; - std::unique_ptr ws_date = - GetFPDFWideString(kDateW); - EXPECT_TRUE( - FPDFAttachment_SetStringValue(attachment, kDateKey, ws_date.get())); - - // Set the checksum to be an arbitrary value. - constexpr wchar_t kCheckSumW[] = L""; - std::unique_ptr ws_checksum = - GetFPDFWideString(kCheckSumW); - EXPECT_TRUE(FPDFAttachment_SetStringValue(attachment, kChecksumKey, - ws_checksum.get())); - - // Verify the name of the new attachment (i.e. the second attachment). - attachment = FPDFDoc_GetAttachment(document(), 1); - ASSERT_TRUE(attachment); - unsigned long len = FPDFAttachment_GetName(attachment, nullptr, 0); - std::vector buf(len); - EXPECT_EQ(12u, FPDFAttachment_GetName(attachment, buf.data(), len)); - EXPECT_STREQ(L"5.txt", - GetPlatformWString(reinterpret_cast(buf.data())) - .c_str()); - - // Verify the content of the new attachment. - len = FPDFAttachment_GetFile(attachment, nullptr, 0); - buf.clear(); - buf.resize(len); - ASSERT_EQ(12u, FPDFAttachment_GetFile(attachment, buf.data(), len)); - EXPECT_EQ(std::string(kContents), std::string(buf.data(), 12)); - - // Verify the creation date of the new attachment. - len = FPDFAttachment_GetStringValue(attachment, kDateKey, nullptr, 0); - buf.clear(); - buf.resize(len); - EXPECT_EQ(48u, FPDFAttachment_GetStringValue(attachment, kDateKey, buf.data(), - len)); - EXPECT_STREQ(kDateW, - GetPlatformWString(reinterpret_cast(buf.data())) - .c_str()); - - // Verify the checksum of the new attachment. - len = FPDFAttachment_GetStringValue(attachment, kChecksumKey, nullptr, 0); - buf.clear(); - buf.resize(len); - EXPECT_EQ(70u, FPDFAttachment_GetStringValue(attachment, kChecksumKey, - buf.data(), len)); - EXPECT_STREQ(kCheckSumW, - GetPlatformWString(reinterpret_cast(buf.data())) - .c_str()); - - // Overwrite the existing file with empty content, and check that the checksum - // gets updated to the correct value. - EXPECT_TRUE(FPDFAttachment_SetFile(attachment, document(), nullptr, 0)); - EXPECT_EQ(0u, FPDFAttachment_GetFile(attachment, nullptr, 0)); - len = FPDFAttachment_GetStringValue(attachment, kChecksumKey, nullptr, 0); - buf.clear(); - buf.resize(len); - EXPECT_EQ(70u, FPDFAttachment_GetStringValue(attachment, kChecksumKey, - buf.data(), len)); - EXPECT_EQ(L"", - GetPlatformWString(reinterpret_cast(buf.data()))); -} - -TEST_F(FPDFAttachmentEmbeddertest, DeleteAttachment) { - // Open a file with two attachments. - ASSERT_TRUE(OpenDocument("embedded_attachments.pdf")); - EXPECT_EQ(2, FPDFDoc_GetAttachmentCount(document())); - - // Verify the name of the first attachment. - FPDF_ATTACHMENT attachment = FPDFDoc_GetAttachment(document(), 0); - unsigned long len = FPDFAttachment_GetName(attachment, nullptr, 0); - std::vector buf(len); - EXPECT_EQ(12u, FPDFAttachment_GetName(attachment, buf.data(), len)); - EXPECT_STREQ(L"1.txt", - GetPlatformWString(reinterpret_cast(buf.data())) - .c_str()); - - // Delete the first attachment. - EXPECT_TRUE(FPDFDoc_DeleteAttachment(document(), 0)); - EXPECT_EQ(1, FPDFDoc_GetAttachmentCount(document())); - - // Verify the name of the new first attachment. - attachment = FPDFDoc_GetAttachment(document(), 0); - len = FPDFAttachment_GetName(attachment, nullptr, 0); - buf.clear(); - buf.resize(len); - EXPECT_EQ(26u, FPDFAttachment_GetName(attachment, buf.data(), len)); - EXPECT_STREQ(L"attached.pdf", - GetPlatformWString(reinterpret_cast(buf.data())) - .c_str()); -} diff --git a/fpdfsdk/fpdfcatalog.cpp b/fpdfsdk/fpdfcatalog.cpp deleted file mode 100644 index af6e40a0ad..0000000000 --- a/fpdfsdk/fpdfcatalog.cpp +++ /dev/null @@ -1,22 +0,0 @@ -// Copyright 2017 PDFium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#include "public/fpdf_catalog.h" - -#include "core/fpdfapi/parser/cpdf_document.h" -#include "fpdfsdk/cpdfsdk_helpers.h" - -FPDF_EXPORT FPDF_BOOL FPDF_CALLCONV -FPDFCatalog_IsTagged(FPDF_DOCUMENT document) { - CPDF_Document* pDoc = CPDFDocumentFromFPDFDocument(document); - if (!pDoc) - return false; - - const CPDF_Dictionary* pCatalog = pDoc->GetRoot(); - if (!pCatalog) - return false; - - const CPDF_Dictionary* pMarkInfo = pCatalog->GetDictFor("MarkInfo"); - return pMarkInfo && pMarkInfo->GetIntegerFor("Marked") != 0; -} diff --git a/fpdfsdk/fpdfcatalog_unittest.cpp b/fpdfsdk/fpdfcatalog_unittest.cpp deleted file mode 100644 index 13c063453b..0000000000 --- a/fpdfsdk/fpdfcatalog_unittest.cpp +++ /dev/null @@ -1,95 +0,0 @@ -// Copyright 2017 PDFium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#include "public/fpdf_catalog.h" - -#include "core/fpdfapi/cpdf_modulemgr.h" -#include "core/fpdfapi/parser/cpdf_document.h" -#include "core/fpdfapi/parser/cpdf_number.h" -#include "core/fpdfapi/parser/cpdf_parser.h" -#include "core/fpdfapi/parser/cpdf_string.h" -#include "testing/gtest/include/gtest/gtest.h" -#include "testing/test_support.h" - -#ifdef PDF_ENABLE_XFA -#include "fpdfsdk/fpdfxfa/cpdfxfa_context.h" -#endif // PDF_ENABLE_XFA - -class CPDF_TestDocument : public CPDF_Document { - public: - CPDF_TestDocument() : CPDF_Document(nullptr) {} - - void SetRoot(CPDF_Dictionary* root) { - m_pRootDict = root; - GetRoot(); - } -}; - -#ifdef PDF_ENABLE_XFA -class CPDF_TestXFAContext : public CPDFXFA_Context { - public: - CPDF_TestXFAContext() - : CPDFXFA_Context(pdfium::MakeUnique()) {} - - void SetRoot(CPDF_Dictionary* root) { - reinterpret_cast(GetPDFDoc())->SetRoot(root); - } - - CPDF_IndirectObjectHolder* GetHolder() { return GetPDFDoc(); } -}; -using CPDF_TestPdfDocument = CPDF_TestXFAContext; -#else // PDF_ENABLE_XFA -using CPDF_TestPdfDocument = CPDF_TestDocument; -#endif // PDF_ENABLE_XFA - -class PDFCatalogTest : public testing::Test { - public: - void SetUp() override { - CPDF_ModuleMgr::Get()->Init(); - - m_pDoc = pdfium::MakeUnique(); - - // Setup the root directory. - m_pRootObj = pdfium::MakeUnique(); - } - - void TearDown() override { - m_pDoc.reset(); - CPDF_ModuleMgr::Destroy(); - } - - protected: - std::unique_ptr m_pDoc; - std::unique_ptr m_pRootObj; -}; - -TEST_F(PDFCatalogTest, IsTagged) { - // Null doc - EXPECT_FALSE(FPDFCatalog_IsTagged(nullptr)); - - // No root - m_pDoc->SetRoot(nullptr); - EXPECT_FALSE(FPDFCatalog_IsTagged(m_pDoc.get())); - - // Empty root - m_pDoc->SetRoot(m_pRootObj.get()); - EXPECT_FALSE(FPDFCatalog_IsTagged(m_pDoc.get())); - - // Root with other key - m_pRootObj->SetNewFor("OTHER_KEY", "other value", false); - EXPECT_FALSE(FPDFCatalog_IsTagged(m_pDoc.get())); - - // Root with empty MarkInfo - CPDF_Dictionary* markInfoDict = - m_pRootObj->SetNewFor("MarkInfo"); - EXPECT_FALSE(FPDFCatalog_IsTagged(m_pDoc.get())); - - // MarkInfo present but Marked is 0 - markInfoDict->SetNewFor("Marked", 0); - EXPECT_FALSE(FPDFCatalog_IsTagged(m_pDoc.get())); - - // MarkInfo present and Marked is 1, PDF is considered tagged. - markInfoDict->SetNewFor("Marked", 1); - EXPECT_TRUE(FPDFCatalog_IsTagged(m_pDoc.get())); -} diff --git a/fpdfsdk/fpdfdoc.cpp b/fpdfsdk/fpdfdoc.cpp deleted file mode 100644 index d8be12066a..0000000000 --- a/fpdfsdk/fpdfdoc.cpp +++ /dev/null @@ -1,429 +0,0 @@ -// Copyright 2014 PDFium Authors. All rights reserved. -// 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_doc.h" - -#include -#include - -#include "core/fpdfapi/page/cpdf_page.h" -#include "core/fpdfapi/parser/cpdf_array.h" -#include "core/fpdfapi/parser/cpdf_document.h" -#include "core/fpdfdoc/cpdf_bookmark.h" -#include "core/fpdfdoc/cpdf_bookmarktree.h" -#include "core/fpdfdoc/cpdf_dest.h" -#include "core/fpdfdoc/cpdf_pagelabel.h" -#include "fpdfsdk/cpdfsdk_helpers.h" -#include "third_party/base/ptr_util.h" -#include "third_party/base/stl_util.h" - -namespace { - -CPDF_Bookmark FindBookmark(const CPDF_BookmarkTree& tree, - CPDF_Bookmark bookmark, - const WideString& title, - std::set* visited) { - // Return if already checked to avoid circular calling. - if (pdfium::ContainsKey(*visited, bookmark.GetDict())) - return CPDF_Bookmark(); - visited->insert(bookmark.GetDict()); - - if (bookmark.GetDict() && - bookmark.GetTitle().CompareNoCase(title.c_str()) == 0) { - // First check this item. - return bookmark; - } - - // Go into children items. - CPDF_Bookmark child = tree.GetFirstChild(bookmark); - while (child.GetDict() && !pdfium::ContainsKey(*visited, child.GetDict())) { - // Check this item and its children. - CPDF_Bookmark found = FindBookmark(tree, child, title, visited); - if (found.GetDict()) - return found; - child = tree.GetNextSibling(child); - } - return CPDF_Bookmark(); -} - -CPDF_LinkList* GetLinkList(CPDF_Page* page) { - if (!page) - return nullptr; - - CPDF_Document* pDoc = page->m_pDocument.Get(); - std::unique_ptr* pHolder = pDoc->LinksContext(); - if (!pHolder->get()) - *pHolder = pdfium::MakeUnique(); - return pHolder->get(); -} - -CPDF_Array* CPDFArrayFromDest(FPDF_DEST dest) { - return static_cast(dest); -} - -CPDF_Dictionary* CPDFDictionaryFromFPDFAction(FPDF_ACTION action) { - return ToDictionary(static_cast(action)); -} - -CPDF_Dictionary* CPDFDictionaryFromFPDFBookmark(FPDF_BOOKMARK bookmark) { - return ToDictionary(static_cast(bookmark)); -} - -CPDF_Dictionary* CPDFDictionaryFromFPDFLink(FPDF_LINK link) { - return ToDictionary(static_cast(link)); -} - -} // namespace - -FPDF_EXPORT FPDF_BOOKMARK FPDF_CALLCONV -FPDFBookmark_GetFirstChild(FPDF_DOCUMENT document, FPDF_BOOKMARK pDict) { - CPDF_Document* pDoc = CPDFDocumentFromFPDFDocument(document); - if (!pDoc) - return nullptr; - CPDF_BookmarkTree tree(pDoc); - CPDF_Bookmark bookmark(CPDFDictionaryFromFPDFBookmark(pDict)); - return tree.GetFirstChild(bookmark).GetDict(); -} - -FPDF_EXPORT FPDF_BOOKMARK FPDF_CALLCONV -FPDFBookmark_GetNextSibling(FPDF_DOCUMENT document, FPDF_BOOKMARK pDict) { - if (!pDict) - return nullptr; - CPDF_Document* pDoc = CPDFDocumentFromFPDFDocument(document); - if (!pDoc) - return nullptr; - CPDF_BookmarkTree tree(pDoc); - CPDF_Bookmark bookmark(CPDFDictionaryFromFPDFBookmark(pDict)); - return tree.GetNextSibling(bookmark).GetDict(); -} - -FPDF_EXPORT unsigned long FPDF_CALLCONV -FPDFBookmark_GetTitle(FPDF_BOOKMARK pDict, void* buffer, unsigned long buflen) { - if (!pDict) - return 0; - CPDF_Bookmark bookmark(CPDFDictionaryFromFPDFBookmark(pDict)); - WideString title = bookmark.GetTitle(); - return Utf16EncodeMaybeCopyAndReturnLength(title, buffer, buflen); -} - -FPDF_EXPORT FPDF_BOOKMARK FPDF_CALLCONV -FPDFBookmark_Find(FPDF_DOCUMENT document, FPDF_WIDESTRING title) { - if (!title || title[0] == 0) - return nullptr; - CPDF_Document* pDoc = CPDFDocumentFromFPDFDocument(document); - if (!pDoc) - return nullptr; - CPDF_BookmarkTree tree(pDoc); - size_t len = WideString::WStringLength(title); - WideString encodedTitle = WideString::FromUTF16LE(title, len); - std::set visited; - return FindBookmark(tree, CPDF_Bookmark(), encodedTitle, &visited).GetDict(); -} - -FPDF_EXPORT FPDF_DEST FPDF_CALLCONV FPDFBookmark_GetDest(FPDF_DOCUMENT document, - FPDF_BOOKMARK pDict) { - if (!pDict) - return nullptr; - CPDF_Document* pDoc = CPDFDocumentFromFPDFDocument(document); - if (!pDoc) - return nullptr; - CPDF_Bookmark bookmark(CPDFDictionaryFromFPDFBookmark(pDict)); - CPDF_Dest dest = bookmark.GetDest(pDoc); - if (dest.GetObject()) - return dest.GetObject(); - // If this bookmark is not directly associated with a dest, we try to get - // action - CPDF_Action action = bookmark.GetAction(); - if (!action.GetDict()) - return nullptr; - return action.GetDest(pDoc).GetObject(); -} - -FPDF_EXPORT FPDF_ACTION FPDF_CALLCONV -FPDFBookmark_GetAction(FPDF_BOOKMARK pDict) { - if (!pDict) - return nullptr; - CPDF_Bookmark bookmark(CPDFDictionaryFromFPDFBookmark(pDict)); - return bookmark.GetAction().GetDict(); -} - -FPDF_EXPORT unsigned long FPDF_CALLCONV FPDFAction_GetType(FPDF_ACTION pDict) { - if (!pDict) - return PDFACTION_UNSUPPORTED; - - CPDF_Action action(CPDFDictionaryFromFPDFAction(pDict)); - CPDF_Action::ActionType type = action.GetType(); - switch (type) { - case CPDF_Action::GoTo: - return PDFACTION_GOTO; - case CPDF_Action::GoToR: - return PDFACTION_REMOTEGOTO; - case CPDF_Action::URI: - return PDFACTION_URI; - case CPDF_Action::Launch: - return PDFACTION_LAUNCH; - default: - return PDFACTION_UNSUPPORTED; - } -} - -FPDF_EXPORT FPDF_DEST FPDF_CALLCONV FPDFAction_GetDest(FPDF_DOCUMENT document, - FPDF_ACTION pDict) { - if (!pDict) - return nullptr; - CPDF_Document* pDoc = CPDFDocumentFromFPDFDocument(document); - if (!pDoc) - return nullptr; - CPDF_Action action(CPDFDictionaryFromFPDFAction(pDict)); - return action.GetDest(pDoc).GetObject(); -} - -FPDF_EXPORT unsigned long FPDF_CALLCONV -FPDFAction_GetFilePath(FPDF_ACTION pDict, void* buffer, unsigned long buflen) { - unsigned long type = FPDFAction_GetType(pDict); - if (type != PDFACTION_REMOTEGOTO && type != PDFACTION_LAUNCH) - return 0; - - CPDF_Action action(CPDFDictionaryFromFPDFAction(pDict)); - ByteString path = action.GetFilePath().UTF8Encode(); - unsigned long len = path.GetLength() + 1; - if (buffer && len <= buflen) - memcpy(buffer, path.c_str(), len); - return len; -} - -FPDF_EXPORT unsigned long FPDF_CALLCONV -FPDFAction_GetURIPath(FPDF_DOCUMENT document, - FPDF_ACTION pDict, - void* buffer, - unsigned long buflen) { - if (!pDict) - return 0; - CPDF_Document* pDoc = CPDFDocumentFromFPDFDocument(document); - if (!pDoc) - return 0; - CPDF_Action action(CPDFDictionaryFromFPDFAction(pDict)); - ByteString path = action.GetURI(pDoc); - unsigned long len = path.GetLength() + 1; - if (buffer && len <= buflen) - memcpy(buffer, path.c_str(), len); - return len; -} - -FPDF_EXPORT unsigned long FPDF_CALLCONV -FPDFDest_GetPageIndex(FPDF_DOCUMENT document, FPDF_DEST dest) { - if (!dest) - return 0; - - CPDF_Document* pDoc = CPDFDocumentFromFPDFDocument(document); - if (!pDoc) - return 0; - - CPDF_Dest destination(CPDFArrayFromDest(dest)); - return destination.GetPageIndexDeprecated(pDoc); -} - -FPDF_EXPORT int FPDF_CALLCONV FPDFDest_GetDestPageIndex(FPDF_DOCUMENT document, - FPDF_DEST dest) { - if (!dest) - return -1; - - CPDF_Document* pDoc = CPDFDocumentFromFPDFDocument(document); - if (!pDoc) - return -1; - - CPDF_Dest destination(CPDFArrayFromDest(dest)); - return destination.GetDestPageIndex(pDoc); -} - -FPDF_EXPORT unsigned long FPDF_CALLCONV -FPDFDest_GetView(FPDF_DEST pDict, - unsigned long* pNumParams, - FS_FLOAT* pParams) { - if (!pDict) { - *pNumParams = 0; - return 0; - } - - CPDF_Dest destination(CPDFArrayFromDest(pDict)); - unsigned long nParams = destination.GetNumParams(); - ASSERT(nParams <= 4); - *pNumParams = nParams; - for (unsigned long i = 0; i < nParams; ++i) - pParams[i] = destination.GetParam(i); - return destination.GetZoomMode(); -} - -FPDF_EXPORT FPDF_BOOL FPDF_CALLCONV -FPDFDest_GetLocationInPage(FPDF_DEST pDict, - FPDF_BOOL* hasXVal, - FPDF_BOOL* hasYVal, - FPDF_BOOL* hasZoomVal, - FS_FLOAT* x, - FS_FLOAT* y, - FS_FLOAT* zoom) { - if (!pDict) - return false; - - auto dest = pdfium::MakeUnique(CPDFArrayFromDest(pDict)); - - // FPDF_BOOL is an int, GetXYZ expects bools. - bool bHasX; - bool bHasY; - bool bHasZoom; - if (!dest->GetXYZ(&bHasX, &bHasY, &bHasZoom, x, y, zoom)) - return false; - - *hasXVal = bHasX; - *hasYVal = bHasY; - *hasZoomVal = bHasZoom; - return true; -} - -FPDF_EXPORT FPDF_LINK FPDF_CALLCONV FPDFLink_GetLinkAtPoint(FPDF_PAGE page, - double x, - double y) { - CPDF_Page* pPage = CPDFPageFromFPDFPage(page); - if (!pPage) - return nullptr; - - CPDF_LinkList* pLinkList = GetLinkList(pPage); - if (!pLinkList) - return nullptr; - - return pLinkList - ->GetLinkAtPoint(pPage, - CFX_PointF(static_cast(x), static_cast(y)), - nullptr) - .GetDict(); -} - -FPDF_EXPORT int FPDF_CALLCONV FPDFLink_GetLinkZOrderAtPoint(FPDF_PAGE page, - double x, - double y) { - CPDF_Page* pPage = CPDFPageFromFPDFPage(page); - if (!pPage) - return -1; - - CPDF_LinkList* pLinkList = GetLinkList(pPage); - if (!pLinkList) - return -1; - - int z_order = -1; - pLinkList->GetLinkAtPoint( - pPage, CFX_PointF(static_cast(x), static_cast(y)), - &z_order); - return z_order; -} - -FPDF_EXPORT FPDF_DEST FPDF_CALLCONV FPDFLink_GetDest(FPDF_DOCUMENT document, - FPDF_LINK pDict) { - if (!pDict) - return nullptr; - CPDF_Document* pDoc = CPDFDocumentFromFPDFDocument(document); - if (!pDoc) - return nullptr; - CPDF_Link link(CPDFDictionaryFromFPDFLink(pDict)); - FPDF_DEST dest = link.GetDest(pDoc).GetObject(); - if (dest) - return dest; - // If this link is not directly associated with a dest, we try to get action - CPDF_Action action = link.GetAction(); - if (!action.GetDict()) - return nullptr; - return action.GetDest(pDoc).GetObject(); -} - -FPDF_EXPORT FPDF_ACTION FPDF_CALLCONV FPDFLink_GetAction(FPDF_LINK pDict) { - if (!pDict) - return nullptr; - - CPDF_Link link(CPDFDictionaryFromFPDFLink(pDict)); - return link.GetAction().GetDict(); -} - -FPDF_EXPORT FPDF_BOOL FPDF_CALLCONV FPDFLink_Enumerate(FPDF_PAGE page, - int* start_pos, - FPDF_LINK* link_annot) { - if (!start_pos || !link_annot) - return false; - CPDF_Page* pPage = CPDFPageFromFPDFPage(page); - if (!pPage || !pPage->m_pFormDict) - return false; - CPDF_Array* pAnnots = pPage->m_pFormDict->GetArrayFor("Annots"); - if (!pAnnots) - return false; - for (size_t i = *start_pos; i < pAnnots->GetCount(); i++) { - CPDF_Dictionary* pDict = ToDictionary(pAnnots->GetDirectObjectAt(i)); - if (!pDict) - continue; - if (pDict->GetStringFor("Subtype") == "Link") { - *start_pos = static_cast(i + 1); - *link_annot = static_cast(pDict); - return true; - } - } - return false; -} - -FPDF_EXPORT FPDF_BOOL FPDF_CALLCONV FPDFLink_GetAnnotRect(FPDF_LINK link_annot, - FS_RECTF* rect) { - if (!link_annot || !rect) - return false; - CPDF_Dictionary* pAnnotDict = CPDFDictionaryFromFPDFLink(link_annot); - FSRECTFFromCFXFloatRect(pAnnotDict->GetRectFor("Rect"), rect); - return true; -} - -FPDF_EXPORT int FPDF_CALLCONV FPDFLink_CountQuadPoints(FPDF_LINK link_annot) { - const CPDF_Array* pArray = - GetQuadPointsArrayFromDictionary(CPDFDictionaryFromFPDFLink(link_annot)); - return pArray ? static_cast(pArray->GetCount() / 8) : 0; -} - -FPDF_EXPORT FPDF_BOOL FPDF_CALLCONV -FPDFLink_GetQuadPoints(FPDF_LINK link_annot, - int quad_index, - FS_QUADPOINTSF* quad_points) { - if (!quad_points || quad_index < 0) - return false; - return GetQuadPointsFromDictionary(CPDFDictionaryFromFPDFLink(link_annot), - static_cast(quad_index), - quad_points); -} - -FPDF_EXPORT unsigned long FPDF_CALLCONV FPDF_GetMetaText(FPDF_DOCUMENT document, - FPDF_BYTESTRING tag, - void* buffer, - unsigned long buflen) { - if (!tag) - return 0; - CPDF_Document* pDoc = CPDFDocumentFromFPDFDocument(document); - if (!pDoc) - return 0; - pDoc->LoadDocumentInfo(); - const CPDF_Dictionary* pInfo = pDoc->GetInfo(); - if (!pInfo) - return 0; - WideString text = pInfo->GetUnicodeTextFor(tag); - return Utf16EncodeMaybeCopyAndReturnLength(text, buffer, buflen); -} - -FPDF_EXPORT unsigned long FPDF_CALLCONV -FPDF_GetPageLabel(FPDF_DOCUMENT document, - int page_index, - void* buffer, - unsigned long buflen) { - if (page_index < 0) - return 0; - - // CPDF_PageLabel can deal with NULL |document|. - CPDF_PageLabel label(CPDFDocumentFromFPDFDocument(document)); - Optional str = label.GetLabel(page_index); - return str.has_value() - ? Utf16EncodeMaybeCopyAndReturnLength(str.value(), buffer, buflen) - : 0; -} diff --git a/fpdfsdk/fpdfdoc_embeddertest.cpp b/fpdfsdk/fpdfdoc_embeddertest.cpp deleted file mode 100644 index 07b83263be..0000000000 --- a/fpdfsdk/fpdfdoc_embeddertest.cpp +++ /dev/null @@ -1,424 +0,0 @@ -// Copyright 2015 PDFium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#include -#include - -#include "core/fxcrt/fx_string.h" -#include "public/fpdf_doc.h" -#include "public/fpdf_edit.h" -#include "public/fpdfview.h" -#include "testing/embedder_test.h" -#include "testing/gtest/include/gtest/gtest.h" -#include "testing/test_support.h" - -class FPDFDocEmbeddertest : public EmbedderTest {}; - -TEST_F(FPDFDocEmbeddertest, DestGetPageIndex) { - EXPECT_TRUE(OpenDocument("named_dests.pdf")); - - // NULL FPDF_DEST case. - EXPECT_EQ(0U, FPDFDest_GetPageIndex(document(), nullptr)); - EXPECT_EQ(-1, FPDFDest_GetDestPageIndex(document(), nullptr)); - - // Page number directly in item from Dests NameTree. - FPDF_DEST dest = FPDF_GetNamedDestByName(document(), "First"); - EXPECT_TRUE(dest); - EXPECT_EQ(1U, FPDFDest_GetPageIndex(document(), dest)); - EXPECT_EQ(1, FPDFDest_GetDestPageIndex(document(), dest)); - - // Page number via object reference in item from Dests NameTree. - dest = FPDF_GetNamedDestByName(document(), "Next"); - EXPECT_TRUE(dest); - EXPECT_EQ(1U, FPDFDest_GetPageIndex(document(), dest)); - EXPECT_EQ(1, FPDFDest_GetDestPageIndex(document(), dest)); - - // Page number directly in item from Dests dictionary. - dest = FPDF_GetNamedDestByName(document(), "FirstAlternate"); - EXPECT_TRUE(dest); - EXPECT_EQ(11U, FPDFDest_GetPageIndex(document(), dest)); - EXPECT_EQ(11, FPDFDest_GetDestPageIndex(document(), dest)); - - // Invalid object reference in item from Dests NameTree. - dest = FPDF_GetNamedDestByName(document(), "LastAlternate"); - EXPECT_TRUE(dest); - EXPECT_EQ(0U, FPDFDest_GetPageIndex(document(), dest)); - EXPECT_EQ(-1, FPDFDest_GetDestPageIndex(document(), dest)); -} - -TEST_F(FPDFDocEmbeddertest, DestGetView) { - EXPECT_TRUE(OpenDocument("named_dests.pdf")); - - unsigned long numParams; - FS_FLOAT params[4]; - - numParams = 42; - std::fill_n(params, 4, 42.4242f); - EXPECT_EQ(static_cast(PDFDEST_VIEW_UNKNOWN_MODE), - FPDFDest_GetView(nullptr, &numParams, params)); - EXPECT_EQ(0U, numParams); - EXPECT_FLOAT_EQ(42.4242f, params[0]); - - numParams = 42; - std::fill_n(params, 4, 42.4242f); - FPDF_DEST dest = FPDF_GetNamedDestByName(document(), "First"); - EXPECT_TRUE(dest); - EXPECT_EQ(static_cast(PDFDEST_VIEW_XYZ), - FPDFDest_GetView(dest, &numParams, params)); - EXPECT_EQ(3U, numParams); - EXPECT_FLOAT_EQ(0, params[0]); - EXPECT_FLOAT_EQ(0, params[1]); - EXPECT_FLOAT_EQ(1, params[2]); - EXPECT_FLOAT_EQ(42.4242f, params[3]); - - numParams = 42; - std::fill_n(params, 4, 42.4242f); - dest = FPDF_GetNamedDestByName(document(), "Next"); - EXPECT_TRUE(dest); - EXPECT_EQ(static_cast(PDFDEST_VIEW_FIT), - FPDFDest_GetView(dest, &numParams, params)); - EXPECT_EQ(0U, numParams); - EXPECT_FLOAT_EQ(42.4242f, params[0]); - - numParams = 42; - std::fill_n(params, 4, 42.4242f); - dest = FPDF_GetNamedDestByName(document(), "FirstAlternate"); - EXPECT_TRUE(dest); - EXPECT_EQ(static_cast(PDFDEST_VIEW_XYZ), - FPDFDest_GetView(dest, &numParams, params)); - EXPECT_EQ(3U, numParams); - EXPECT_FLOAT_EQ(200, params[0]); - EXPECT_FLOAT_EQ(400, params[1]); - EXPECT_FLOAT_EQ(800, params[2]); - EXPECT_FLOAT_EQ(42.4242f, params[3]); - - numParams = 42; - std::fill_n(params, 4, 42.4242f); - dest = FPDF_GetNamedDestByName(document(), "LastAlternate"); - EXPECT_TRUE(dest); - EXPECT_EQ(static_cast(PDFDEST_VIEW_XYZ), - FPDFDest_GetView(dest, &numParams, params)); - EXPECT_EQ(3U, numParams); - EXPECT_FLOAT_EQ(0, params[0]); - EXPECT_FLOAT_EQ(0, params[1]); - EXPECT_FLOAT_EQ(-200, params[2]); - EXPECT_FLOAT_EQ(42.4242f, params[3]); -} - -TEST_F(FPDFDocEmbeddertest, DestGetLocationInPage) { - EXPECT_TRUE(OpenDocument("named_dests.pdf")); - - // NULL FPDF_DEST case. - EXPECT_EQ(0U, FPDFDest_GetPageIndex(document(), nullptr)); - EXPECT_EQ(-1, FPDFDest_GetDestPageIndex(document(), nullptr)); - - FPDF_DEST dest = FPDF_GetNamedDestByName(document(), "First"); - EXPECT_TRUE(dest); - - FPDF_BOOL hasX; - FPDF_BOOL hasY; - FPDF_BOOL hasZoom; - FS_FLOAT x; - FS_FLOAT y; - FS_FLOAT zoom; - EXPECT_TRUE( - FPDFDest_GetLocationInPage(dest, &hasX, &hasY, &hasZoom, &x, &y, &zoom)); - EXPECT_TRUE(hasX); - EXPECT_TRUE(hasY); - EXPECT_TRUE(hasZoom); - EXPECT_EQ(0, x); - EXPECT_EQ(0, y); - EXPECT_EQ(1, zoom); -} - -TEST_F(FPDFDocEmbeddertest, BUG_680376) { - EXPECT_TRUE(OpenDocument("bug_680376.pdf")); - - // Page number directly in item from Dests NameTree. - FPDF_DEST dest = FPDF_GetNamedDestByName(document(), "First"); - EXPECT_TRUE(dest); - EXPECT_EQ(static_cast(-1), - FPDFDest_GetPageIndex(document(), dest)); - EXPECT_EQ(-1, FPDFDest_GetDestPageIndex(document(), dest)); -} - -TEST_F(FPDFDocEmbeddertest, BUG_821454) { - EXPECT_TRUE(OpenDocument("bug_821454.pdf")); - - FPDF_PAGE page = LoadPage(0); - ASSERT_TRUE(page); - - FPDF_LINK link1 = FPDFLink_GetLinkAtPoint(page, 150, 360); - ASSERT_TRUE(link1); - FPDF_LINK link2 = FPDFLink_GetLinkAtPoint(page, 150, 420); - ASSERT_TRUE(link2); - - FPDF_DEST dest1 = FPDFLink_GetDest(document(), link1); - ASSERT_TRUE(dest1); - FPDF_DEST dest2 = FPDFLink_GetDest(document(), link2); - ASSERT_TRUE(dest2); - - EXPECT_EQ(0, FPDFDest_GetDestPageIndex(document(), dest1)); - EXPECT_EQ(0, FPDFDest_GetDestPageIndex(document(), dest2)); - - { - FPDF_BOOL has_x_coord; - FPDF_BOOL has_y_coord; - FPDF_BOOL has_zoom; - FS_FLOAT x; - FS_FLOAT y; - FS_FLOAT zoom; - FPDF_BOOL success = FPDFDest_GetLocationInPage( - dest1, &has_x_coord, &has_y_coord, &has_zoom, &x, &y, &zoom); - ASSERT_TRUE(success); - EXPECT_TRUE(has_x_coord); - EXPECT_TRUE(has_y_coord); - EXPECT_FALSE(has_zoom); - EXPECT_FLOAT_EQ(100.0f, x); - EXPECT_FLOAT_EQ(200.0f, y); - } - { - FPDF_BOOL has_x_coord; - FPDF_BOOL has_y_coord; - FPDF_BOOL has_zoom; - FS_FLOAT x; - FS_FLOAT y; - FS_FLOAT zoom; - FPDF_BOOL success = FPDFDest_GetLocationInPage( - dest2, &has_x_coord, &has_y_coord, &has_zoom, &x, &y, &zoom); - ASSERT_TRUE(success); - EXPECT_TRUE(has_x_coord); - EXPECT_TRUE(has_y_coord); - EXPECT_FALSE(has_zoom); - EXPECT_FLOAT_EQ(150.0f, x); - EXPECT_FLOAT_EQ(250.0f, y); - } - - UnloadPage(page); -} - -TEST_F(FPDFDocEmbeddertest, ActionGetFilePath) { - EXPECT_TRUE(OpenDocument("launch_action.pdf")); - - FPDF_PAGE page = LoadPage(0); - ASSERT_TRUE(page); - - // The target action is nearly the size of the whole page. - FPDF_LINK link = FPDFLink_GetLinkAtPoint(page, 100, 100); - ASSERT_TRUE(link); - - FPDF_ACTION action = FPDFLink_GetAction(link); - ASSERT_TRUE(action); - - const char kExpectedResult[] = "test.pdf"; - const unsigned long kExpectedLength = sizeof(kExpectedResult); - unsigned long bufsize = FPDFAction_GetFilePath(action, nullptr, 0); - ASSERT_EQ(kExpectedLength, bufsize); - - char buf[kExpectedLength]; - EXPECT_EQ(bufsize, FPDFAction_GetFilePath(action, buf, bufsize)); - EXPECT_EQ(std::string(kExpectedResult), std::string(buf)); - - UnloadPage(page); -} - -TEST_F(FPDFDocEmbeddertest, NoBookmarks) { - // Open a file with no bookmarks. - EXPECT_TRUE(OpenDocument("named_dests.pdf")); - - // The non-existent top-level bookmark has no title. - unsigned short buf[128]; - EXPECT_EQ(0u, FPDFBookmark_GetTitle(nullptr, buf, sizeof(buf))); - - // The non-existent top-level bookmark has no children. - EXPECT_EQ(nullptr, FPDFBookmark_GetFirstChild(document(), nullptr)); -} - -TEST_F(FPDFDocEmbeddertest, Bookmarks) { - // Open a file with two bookmarks. - EXPECT_TRUE(OpenDocument("bookmarks.pdf")); - - // The existent top-level bookmark has no title. - unsigned short buf[128]; - EXPECT_EQ(0u, FPDFBookmark_GetTitle(nullptr, buf, sizeof(buf))); - - FPDF_BOOKMARK child = FPDFBookmark_GetFirstChild(document(), nullptr); - EXPECT_TRUE(child); - EXPECT_EQ(34u, FPDFBookmark_GetTitle(child, buf, sizeof(buf))); - EXPECT_EQ(WideString(L"A Good Beginning"), WideString::FromUTF16LE(buf, 16)); - - EXPECT_EQ(nullptr, FPDFBookmark_GetFirstChild(document(), child)); - - FPDF_BOOKMARK sibling = FPDFBookmark_GetNextSibling(document(), child); - EXPECT_TRUE(sibling); - EXPECT_EQ(28u, FPDFBookmark_GetTitle(sibling, buf, sizeof(buf))); - EXPECT_EQ(WideString(L"A Good Ending"), WideString::FromUTF16LE(buf, 13)); - - EXPECT_EQ(nullptr, FPDFBookmark_GetNextSibling(document(), sibling)); -} - -TEST_F(FPDFDocEmbeddertest, FindBookmarks) { - // Open a file with two bookmarks. - EXPECT_TRUE(OpenDocument("bookmarks.pdf")); - - // Find the first one, based on its known title. - std::unique_ptr title = - GetFPDFWideString(L"A Good Beginning"); - FPDF_BOOKMARK child = FPDFBookmark_Find(document(), title.get()); - EXPECT_TRUE(child); - - // Check that the string matches. - unsigned short buf[128]; - EXPECT_EQ(34u, FPDFBookmark_GetTitle(child, buf, sizeof(buf))); - EXPECT_EQ(WideString(L"A Good Beginning"), WideString::FromUTF16LE(buf, 16)); - - // Check that it is them same as the one returned by GetFirstChild. - EXPECT_EQ(child, FPDFBookmark_GetFirstChild(document(), nullptr)); - - // Try to find one using a non-existent title. - std::unique_ptr bad_title = - GetFPDFWideString(L"A BAD Beginning"); - EXPECT_EQ(nullptr, FPDFBookmark_Find(document(), bad_title.get())); -} - -// Check circular bookmarks will not cause infinite loop. -TEST_F(FPDFDocEmbeddertest, FindBookmarks_bug420) { - // Open a file with circular bookmarks. - EXPECT_TRUE(OpenDocument("bookmarks_circular.pdf")); - - // Try to find a title. - std::unique_ptr title = - GetFPDFWideString(L"anything"); - EXPECT_EQ(nullptr, FPDFBookmark_Find(document(), title.get())); -} - -TEST_F(FPDFDocEmbeddertest, DeletePage) { - EXPECT_TRUE(OpenDocument("hello_world.pdf")); - EXPECT_EQ(1, FPDF_GetPageCount(document())); - FPDFPage_Delete(document(), 0); - EXPECT_EQ(0, FPDF_GetPageCount(document())); -} - -TEST_F(FPDFDocEmbeddertest, GetMetaText) { - ASSERT_TRUE(OpenDocument("bug_601362.pdf")); - - // Invalid document / tag results in 0. - unsigned short buf[128]; - EXPECT_EQ(0u, FPDF_GetMetaText(document(), nullptr, buf, sizeof(buf))); - EXPECT_EQ(0u, FPDF_GetMetaText(nullptr, "", buf, sizeof(buf))); - - // Tags that do not eixst results in an empty wide string. - EXPECT_EQ(2u, FPDF_GetMetaText(document(), "", buf, sizeof(buf))); - EXPECT_EQ(2u, FPDF_GetMetaText(document(), "foo", buf, sizeof(buf))); - ASSERT_EQ(2u, FPDF_GetMetaText(document(), "Title", buf, sizeof(buf))); - ASSERT_EQ(2u, FPDF_GetMetaText(document(), "Author", buf, sizeof(buf))); - ASSERT_EQ(2u, FPDF_GetMetaText(document(), "Subject", buf, sizeof(buf))); - ASSERT_EQ(2u, FPDF_GetMetaText(document(), "Keywords", buf, sizeof(buf))); - ASSERT_EQ(2u, FPDF_GetMetaText(document(), "Producer", buf, sizeof(buf))); - - constexpr wchar_t kExpectedCreator[] = L"Microsoft Word"; - ASSERT_EQ(30u, FPDF_GetMetaText(document(), "Creator", buf, sizeof(buf))); - EXPECT_EQ(WideString(kExpectedCreator), - WideString::FromUTF16LE(buf, FXSYS_len(kExpectedCreator))); - - constexpr wchar_t kExpectedCreationDate[] = L"D:20160411190039+00'00'"; - ASSERT_EQ(48u, - FPDF_GetMetaText(document(), "CreationDate", buf, sizeof(buf))); - EXPECT_EQ(WideString(kExpectedCreationDate), - WideString::FromUTF16LE(buf, FXSYS_len(kExpectedCreationDate))); - - constexpr wchar_t kExpectedModDate[] = L"D:20160411190039+00'00'"; - ASSERT_EQ(48u, FPDF_GetMetaText(document(), "ModDate", buf, sizeof(buf))); - EXPECT_EQ(WideString(kExpectedModDate), - WideString::FromUTF16LE(buf, FXSYS_len(kExpectedModDate))); -} - -TEST_F(FPDFDocEmbeddertest, GetMetaTextSameObjectNumber) { - ASSERT_TRUE(OpenDocument("annotation_highlight_square_with_ap.pdf")); - - // The PDF has been edited. It has two %%EOF markers, and 2 objects numbered - // (1 0). Both objects are /Info dictionaries, but contain different data. - // Make sure ModDate is the date of the last modification. - unsigned short buf[128]; - constexpr wchar_t kExpectedModDate[] = L"D:20170612232940-04'00'"; - ASSERT_EQ(48u, FPDF_GetMetaText(document(), "ModDate", buf, sizeof(buf))); - EXPECT_EQ(WideString(kExpectedModDate), - WideString::FromUTF16LE(buf, FXSYS_len(kExpectedModDate))); -} - -TEST_F(FPDFDocEmbeddertest, GetMetaTextInAttachmentFile) { - ASSERT_TRUE(OpenDocument("embedded_attachments.pdf")); - - // Make sure this is the date from the PDF itself and not the attached PDF. - unsigned short buf[128]; - constexpr wchar_t kExpectedModDate[] = L"D:20170712214448-07'00'"; - ASSERT_EQ(48u, FPDF_GetMetaText(document(), "ModDate", buf, sizeof(buf))); - EXPECT_EQ(WideString(kExpectedModDate), - WideString::FromUTF16LE(buf, FXSYS_len(kExpectedModDate))); -} - -TEST_F(FPDFDocEmbeddertest, GetMetaTextFromNewDocument) { - FPDF_DOCUMENT empty_doc = FPDF_CreateNewDocument(); - unsigned short buf[128]; - EXPECT_EQ(2u, FPDF_GetMetaText(empty_doc, "Title", buf, sizeof(buf))); - FPDF_CloseDocument(empty_doc); -} - -TEST_F(FPDFDocEmbeddertest, NoPageLabels) { - EXPECT_TRUE(OpenDocument("about_blank.pdf")); - EXPECT_EQ(1, FPDF_GetPageCount(document())); - - ASSERT_EQ(0u, FPDF_GetPageLabel(document(), 0, nullptr, 0)); -} - -TEST_F(FPDFDocEmbeddertest, GetPageLabels) { - EXPECT_TRUE(OpenDocument("page_labels.pdf")); - EXPECT_EQ(7, FPDF_GetPageCount(document())); - - // We do not request labels, when use FPDFAvail_IsXXXAvail. - // Flush all data, to allow read labels. - SetWholeFileAvailable(); - - unsigned short buf[128]; - EXPECT_EQ(0u, FPDF_GetPageLabel(document(), -2, buf, sizeof(buf))); - EXPECT_EQ(0u, FPDF_GetPageLabel(document(), -1, buf, sizeof(buf))); - - const wchar_t kExpectedPageLabel0[] = L"i"; - ASSERT_EQ(4u, FPDF_GetPageLabel(document(), 0, buf, sizeof(buf))); - EXPECT_EQ(WideString(kExpectedPageLabel0), - WideString::FromUTF16LE(buf, FXSYS_len(kExpectedPageLabel0))); - - const wchar_t kExpectedPageLabel1[] = L"ii"; - ASSERT_EQ(6u, FPDF_GetPageLabel(document(), 1, buf, sizeof(buf))); - EXPECT_EQ(WideString(kExpectedPageLabel1), - WideString::FromUTF16LE(buf, FXSYS_len(kExpectedPageLabel1))); - - const wchar_t kExpectedPageLabel2[] = L"1"; - ASSERT_EQ(4u, FPDF_GetPageLabel(document(), 2, buf, sizeof(buf))); - EXPECT_EQ(WideString(kExpectedPageLabel2), - WideString::FromUTF16LE(buf, FXSYS_len(kExpectedPageLabel2))); - - const wchar_t kExpectedPageLabel3[] = L"2"; - ASSERT_EQ(4u, FPDF_GetPageLabel(document(), 3, buf, sizeof(buf))); - EXPECT_EQ(WideString(kExpectedPageLabel3), - WideString::FromUTF16LE(buf, FXSYS_len(kExpectedPageLabel3))); - - const wchar_t kExpectedPageLabel4[] = L"zzA"; - ASSERT_EQ(8u, FPDF_GetPageLabel(document(), 4, buf, sizeof(buf))); - EXPECT_EQ(WideString(kExpectedPageLabel4), - WideString::FromUTF16LE(buf, FXSYS_len(kExpectedPageLabel4))); - - const wchar_t kExpectedPageLabel5[] = L"zzB"; - ASSERT_EQ(8u, FPDF_GetPageLabel(document(), 5, buf, sizeof(buf))); - EXPECT_EQ(WideString(kExpectedPageLabel5), - WideString::FromUTF16LE(buf, FXSYS_len(kExpectedPageLabel5))); - - const wchar_t kExpectedPageLabel6[] = L""; - ASSERT_EQ(2u, FPDF_GetPageLabel(document(), 6, buf, sizeof(buf))); - EXPECT_EQ(WideString(kExpectedPageLabel6), - WideString::FromUTF16LE(buf, FXSYS_len(kExpectedPageLabel6))); - - ASSERT_EQ(0u, FPDF_GetPageLabel(document(), 7, buf, sizeof(buf))); - ASSERT_EQ(0u, FPDF_GetPageLabel(document(), 8, buf, sizeof(buf))); -} diff --git a/fpdfsdk/fpdfdoc_unittest.cpp b/fpdfsdk/fpdfdoc_unittest.cpp deleted file mode 100644 index b52cccf098..0000000000 --- a/fpdfsdk/fpdfdoc_unittest.cpp +++ /dev/null @@ -1,271 +0,0 @@ -// Copyright 2016 PDFium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#include "public/fpdf_doc.h" - -#include -#include - -#include "core/fpdfapi/cpdf_modulemgr.h" -#include "core/fpdfapi/parser/cpdf_array.h" -#include "core/fpdfapi/parser/cpdf_document.h" -#include "core/fpdfapi/parser/cpdf_name.h" -#include "core/fpdfapi/parser/cpdf_null.h" -#include "core/fpdfapi/parser/cpdf_number.h" -#include "core/fpdfapi/parser/cpdf_parser.h" -#include "core/fpdfapi/parser/cpdf_reference.h" -#include "core/fpdfapi/parser/cpdf_string.h" -#include "core/fpdfdoc/cpdf_dest.h" -#include "testing/gtest/include/gtest/gtest.h" -#include "testing/test_support.h" -#include "third_party/base/ptr_util.h" - -#ifdef PDF_ENABLE_XFA -#include "fpdfsdk/fpdfxfa/cpdfxfa_context.h" -#endif // PDF_ENABLE_XFA - -class CPDF_TestDocument : public CPDF_Document { - public: - CPDF_TestDocument() : CPDF_Document(nullptr) {} - - void SetRoot(CPDF_Dictionary* root) { m_pRootDict = root; } - CPDF_IndirectObjectHolder* GetHolder() { return this; } -}; - -#ifdef PDF_ENABLE_XFA -class CPDF_TestXFAContext : public CPDFXFA_Context { - public: - CPDF_TestXFAContext() - : CPDFXFA_Context(pdfium::MakeUnique()) {} - - void SetRoot(CPDF_Dictionary* root) { - reinterpret_cast(GetPDFDoc())->SetRoot(root); - } - - CPDF_IndirectObjectHolder* GetHolder() { return GetPDFDoc(); } -}; -using CPDF_TestPdfDocument = CPDF_TestXFAContext; -#else // PDF_ENABLE_XFA -using CPDF_TestPdfDocument = CPDF_TestDocument; -#endif // PDF_ENABLE_XFA - -class PDFDocTest : public testing::Test { - public: - struct DictObjInfo { - uint32_t num; - CPDF_Dictionary* obj; - }; - - void SetUp() override { - CPDF_ModuleMgr::Get()->Init(); - - m_pDoc = pdfium::MakeUnique(); - m_pIndirectObjs = m_pDoc->GetHolder(); - - // Setup the root directory. - m_pRootObj = pdfium::MakeUnique(); - m_pDoc->SetRoot(m_pRootObj.get()); - } - - void TearDown() override { - m_pRootObj.reset(); - m_pIndirectObjs = nullptr; - m_pDoc.reset(); - CPDF_ModuleMgr::Destroy(); - } - - std::vector CreateDictObjs(int num) { - std::vector info; - for (int i = 0; i < num; ++i) { - // Objects created will be released by the document. - CPDF_Dictionary* obj = m_pIndirectObjs->NewIndirect(); - info.push_back({obj->GetObjNum(), obj}); - } - return info; - } - - protected: - std::unique_ptr m_pDoc; - UnownedPtr m_pIndirectObjs; - std::unique_ptr m_pRootObj; -}; - -TEST_F(PDFDocTest, FindBookmark) { - { - // No bookmark information. - std::unique_ptr title = - GetFPDFWideString(L""); - EXPECT_EQ(nullptr, FPDFBookmark_Find(m_pDoc.get(), title.get())); - - title = GetFPDFWideString(L"Preface"); - EXPECT_EQ(nullptr, FPDFBookmark_Find(m_pDoc.get(), title.get())); - } - { - // Empty bookmark tree. - m_pRootObj->SetNewFor("Outlines"); - std::unique_ptr title = - GetFPDFWideString(L""); - EXPECT_EQ(nullptr, FPDFBookmark_Find(m_pDoc.get(), title.get())); - - title = GetFPDFWideString(L"Preface"); - EXPECT_EQ(nullptr, FPDFBookmark_Find(m_pDoc.get(), title.get())); - } - { - // Check on a regular bookmark tree. - auto bookmarks = CreateDictObjs(3); - - bookmarks[1].obj->SetNewFor("Title", L"Chapter 1"); - bookmarks[1].obj->SetNewFor("Parent", m_pIndirectObjs.Get(), - bookmarks[0].num); - bookmarks[1].obj->SetNewFor("Next", m_pIndirectObjs.Get(), - bookmarks[2].num); - - bookmarks[2].obj->SetNewFor("Title", L"Chapter 2"); - bookmarks[2].obj->SetNewFor("Parent", m_pIndirectObjs.Get(), - bookmarks[0].num); - bookmarks[2].obj->SetNewFor("Prev", m_pIndirectObjs.Get(), - bookmarks[1].num); - - bookmarks[0].obj->SetNewFor("Type", "Outlines"); - bookmarks[0].obj->SetNewFor("Count", 2); - bookmarks[0].obj->SetNewFor("First", m_pIndirectObjs.Get(), - bookmarks[1].num); - bookmarks[0].obj->SetNewFor("Last", m_pIndirectObjs.Get(), - bookmarks[2].num); - - m_pRootObj->SetNewFor("Outlines", m_pIndirectObjs.Get(), - bookmarks[0].num); - - // Title with no match. - std::unique_ptr title = - GetFPDFWideString(L"Chapter 3"); - EXPECT_EQ(nullptr, FPDFBookmark_Find(m_pDoc.get(), title.get())); - - // Title with partial match only. - title = GetFPDFWideString(L"Chapter"); - EXPECT_EQ(nullptr, FPDFBookmark_Find(m_pDoc.get(), title.get())); - - // Title with a match. - title = GetFPDFWideString(L"Chapter 2"); - EXPECT_EQ(bookmarks[2].obj, FPDFBookmark_Find(m_pDoc.get(), title.get())); - - // Title match is case insensitive. - title = GetFPDFWideString(L"cHaPter 2"); - EXPECT_EQ(bookmarks[2].obj, FPDFBookmark_Find(m_pDoc.get(), title.get())); - } - { - // Circular bookmarks in depth. - auto bookmarks = CreateDictObjs(3); - - bookmarks[1].obj->SetNewFor("Title", L"Chapter 1"); - bookmarks[1].obj->SetNewFor("Parent", m_pIndirectObjs.Get(), - bookmarks[0].num); - bookmarks[1].obj->SetNewFor("First", m_pIndirectObjs.Get(), - bookmarks[2].num); - - bookmarks[2].obj->SetNewFor("Title", L"Chapter 2"); - bookmarks[2].obj->SetNewFor("Parent", m_pIndirectObjs.Get(), - bookmarks[1].num); - bookmarks[2].obj->SetNewFor("First", m_pIndirectObjs.Get(), - bookmarks[1].num); - - bookmarks[0].obj->SetNewFor("Type", "Outlines"); - bookmarks[0].obj->SetNewFor("Count", 2); - bookmarks[0].obj->SetNewFor("First", m_pIndirectObjs.Get(), - bookmarks[1].num); - bookmarks[0].obj->SetNewFor("Last", m_pIndirectObjs.Get(), - bookmarks[2].num); - - m_pRootObj->SetNewFor("Outlines", m_pIndirectObjs.Get(), - bookmarks[0].num); - - // Title with no match. - std::unique_ptr title = - GetFPDFWideString(L"Chapter 3"); - EXPECT_EQ(nullptr, FPDFBookmark_Find(m_pDoc.get(), title.get())); - - // Title with a match. - title = GetFPDFWideString(L"Chapter 2"); - EXPECT_EQ(bookmarks[2].obj, FPDFBookmark_Find(m_pDoc.get(), title.get())); - } - { - // Circular bookmarks in breadth. - auto bookmarks = CreateDictObjs(4); - - bookmarks[1].obj->SetNewFor("Title", L"Chapter 1"); - bookmarks[1].obj->SetNewFor("Parent", m_pIndirectObjs.Get(), - bookmarks[0].num); - bookmarks[1].obj->SetNewFor("Next", m_pIndirectObjs.Get(), - bookmarks[2].num); - - bookmarks[2].obj->SetNewFor("Title", L"Chapter 2"); - bookmarks[2].obj->SetNewFor("Parent", m_pIndirectObjs.Get(), - bookmarks[0].num); - bookmarks[2].obj->SetNewFor("Next", m_pIndirectObjs.Get(), - bookmarks[3].num); - - bookmarks[3].obj->SetNewFor("Title", L"Chapter 3"); - bookmarks[3].obj->SetNewFor("Parent", m_pIndirectObjs.Get(), - bookmarks[0].num); - bookmarks[3].obj->SetNewFor("Next", m_pIndirectObjs.Get(), - bookmarks[1].num); - - bookmarks[0].obj->SetNewFor("Type", "Outlines"); - bookmarks[0].obj->SetNewFor("Count", 2); - bookmarks[0].obj->SetNewFor("First", m_pIndirectObjs.Get(), - bookmarks[1].num); - bookmarks[0].obj->SetNewFor("Last", m_pIndirectObjs.Get(), - bookmarks[2].num); - - m_pRootObj->SetNewFor("Outlines", m_pIndirectObjs.Get(), - bookmarks[0].num); - - // Title with no match. - std::unique_ptr title = - GetFPDFWideString(L"Chapter 8"); - EXPECT_EQ(nullptr, FPDFBookmark_Find(m_pDoc.get(), title.get())); - - // Title with a match. - title = GetFPDFWideString(L"Chapter 3"); - EXPECT_EQ(bookmarks[3].obj, FPDFBookmark_Find(m_pDoc.get(), title.get())); - } -} - -TEST_F(PDFDocTest, GetLocationInPage) { - auto array = pdfium::MakeUnique(); - array->AddNew(0); // Page Index. - array->AddNew("XYZ"); - array->AddNew(4); // X - array->AddNew(5); // Y - array->AddNew(6); // Zoom. - - FPDF_BOOL hasX; - FPDF_BOOL hasY; - FPDF_BOOL hasZoom; - FS_FLOAT x; - FS_FLOAT y; - FS_FLOAT zoom; - - EXPECT_TRUE(FPDFDest_GetLocationInPage(array.get(), &hasX, &hasY, &hasZoom, - &x, &y, &zoom)); - EXPECT_TRUE(hasX); - EXPECT_TRUE(hasY); - EXPECT_TRUE(hasZoom); - EXPECT_EQ(4, x); - EXPECT_EQ(5, y); - EXPECT_EQ(6, zoom); - - array->SetNewAt(2); - array->SetNewAt(3); - array->SetNewAt(4); - EXPECT_TRUE(FPDFDest_GetLocationInPage(array.get(), &hasX, &hasY, &hasZoom, - &x, &y, &zoom)); - EXPECT_FALSE(hasX); - EXPECT_FALSE(hasY); - EXPECT_FALSE(hasZoom); - - array = pdfium::MakeUnique(); - EXPECT_FALSE(FPDFDest_GetLocationInPage(array.get(), &hasX, &hasY, &hasZoom, - &x, &y, &zoom)); -} diff --git a/fpdfsdk/fpdfedit_embeddertest.cpp b/fpdfsdk/fpdfedit_embeddertest.cpp deleted file mode 100644 index 7ea22d0aa4..0000000000 --- a/fpdfsdk/fpdfedit_embeddertest.cpp +++ /dev/null @@ -1,1469 +0,0 @@ -// Copyright 2016 PDFium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#include -#include -#include -#include - -#include "core/fpdfapi/font/cpdf_font.h" -#include "core/fpdfapi/page/cpdf_page.h" -#include "core/fpdfapi/parser/cpdf_array.h" -#include "core/fpdfapi/parser/cpdf_dictionary.h" -#include "core/fpdfapi/parser/cpdf_number.h" -#include "core/fpdfapi/parser/cpdf_stream.h" -#include "core/fxcrt/fx_system.h" -#include "fpdfsdk/cpdfsdk_helpers.h" -#include "public/cpp/fpdf_deleters.h" -#include "public/fpdf_annot.h" -#include "public/fpdf_edit.h" -#include "public/fpdfview.h" -#include "testing/embedder_test.h" -#include "testing/gmock/include/gmock/gmock-matchers.h" -#include "testing/gtest/include/gtest/gtest.h" -#include "testing/test_support.h" - -class FPDFEditEmbeddertest : public EmbedderTest { - protected: - FPDF_DOCUMENT CreateNewDocument() { - document_ = FPDF_CreateNewDocument(); - cpdf_doc_ = CPDFDocumentFromFPDFDocument(document_); - return document_; - } - - void CheckFontDescriptor(CPDF_Dictionary* font_dict, - int font_type, - bool bold, - bool italic, - uint32_t size, - const uint8_t* data) { - CPDF_Dictionary* font_desc = font_dict->GetDictFor("FontDescriptor"); - ASSERT_TRUE(font_desc); - EXPECT_EQ("FontDescriptor", font_desc->GetStringFor("Type")); - EXPECT_EQ(font_dict->GetStringFor("BaseFont"), - font_desc->GetStringFor("FontName")); - - // Check that the font descriptor has the required keys according to spec - // 1.7 Table 5.19 - ASSERT_TRUE(font_desc->KeyExist("Flags")); - - int font_flags = font_desc->GetIntegerFor("Flags"); - EXPECT_EQ(bold, FontStyleIsBold(font_flags)); - EXPECT_EQ(italic, FontStyleIsItalic(font_flags)); - EXPECT_TRUE(FontStyleIsNonSymbolic(font_flags)); - ASSERT_TRUE(font_desc->KeyExist("FontBBox")); - - CPDF_Array* fontBBox = font_desc->GetArrayFor("FontBBox"); - ASSERT_TRUE(fontBBox); - EXPECT_EQ(4U, fontBBox->GetCount()); - // Check that the coordinates are in the preferred order according to spec - // 1.7 Section 3.8.4 - EXPECT_TRUE(fontBBox->GetIntegerAt(0) < fontBBox->GetIntegerAt(2)); - EXPECT_TRUE(fontBBox->GetIntegerAt(1) < fontBBox->GetIntegerAt(3)); - - EXPECT_TRUE(font_desc->KeyExist("ItalicAngle")); - EXPECT_TRUE(font_desc->KeyExist("Ascent")); - EXPECT_TRUE(font_desc->KeyExist("Descent")); - EXPECT_TRUE(font_desc->KeyExist("CapHeight")); - EXPECT_TRUE(font_desc->KeyExist("StemV")); - ByteString present("FontFile"); - ByteString absent("FontFile2"); - if (font_type == FPDF_FONT_TRUETYPE) - std::swap(present, absent); - EXPECT_TRUE(font_desc->KeyExist(present)); - EXPECT_FALSE(font_desc->KeyExist(absent)); - - // Check that the font stream is the one that was provided - CPDF_Stream* font_stream = font_desc->GetStreamFor(present); - ASSERT_EQ(size, font_stream->GetRawSize()); - if (font_type == FPDF_FONT_TRUETYPE) { - ASSERT_EQ(static_cast(size), - font_stream->GetDict()->GetIntegerFor("Length1")); - } - uint8_t* stream_data = font_stream->GetRawData(); - for (size_t j = 0; j < size; j++) - EXPECT_EQ(data[j], stream_data[j]) << " at byte " << j; - } - - void CheckCompositeFontWidths(CPDF_Array* widths_array, - CPDF_Font* typed_font) { - // Check that W array is in a format that conforms to PDF spec 1.7 section - // "Glyph Metrics in CIDFonts" (these checks are not - // implementation-specific). - EXPECT_GT(widths_array->GetCount(), 1U); - int num_cids_checked = 0; - int cur_cid = 0; - for (size_t idx = 0; idx < widths_array->GetCount(); idx++) { - int cid = widths_array->GetNumberAt(idx); - EXPECT_GE(cid, cur_cid); - ASSERT_FALSE(++idx == widths_array->GetCount()); - CPDF_Object* next = widths_array->GetObjectAt(idx); - if (next->IsArray()) { - // We are in the c [w1 w2 ...] case - CPDF_Array* arr = next->AsArray(); - int cnt = static_cast(arr->GetCount()); - size_t inner_idx = 0; - for (cur_cid = cid; cur_cid < cid + cnt; cur_cid++) { - uint32_t width = arr->GetNumberAt(inner_idx++); - EXPECT_EQ(width, typed_font->GetCharWidthF(cur_cid)) << " at cid " - << cur_cid; - } - num_cids_checked += cnt; - continue; - } - // Otherwise, are in the c_first c_last w case. - ASSERT_TRUE(next->IsNumber()); - int last_cid = next->AsNumber()->GetInteger(); - ASSERT_FALSE(++idx == widths_array->GetCount()); - uint32_t width = widths_array->GetNumberAt(idx); - for (cur_cid = cid; cur_cid <= last_cid; cur_cid++) { - EXPECT_EQ(width, typed_font->GetCharWidthF(cur_cid)) << " at cid " - << cur_cid; - } - num_cids_checked += last_cid - cid + 1; - } - // Make sure we have a good amount of cids described - EXPECT_GT(num_cids_checked, 900); - } - CPDF_Document* cpdf_doc() { return cpdf_doc_; } - - private: - CPDF_Document* cpdf_doc_; -}; - -namespace { - -const char kExpectedPDF[] = - "%PDF-1.7\r\n" - "%\xA1\xB3\xC5\xD7\r\n" - "1 0 obj\r\n" - "<>\r\n" - "endobj\r\n" - "2 0 obj\r\n" - "<>\r\n" - "endobj\r\n" - "3 0 obj\r\n" - "<>\r\n" - "endobj\r\n" - "4 0 obj\r\n" - "<>>>" - "/Rotate 0/Type/Page" - ">>\r\n" - "endobj\r\n" - "5 0 obj\r\n" - "<>\r\n" - "endobj\r\n" - "xref\r\n" - "0 6\r\n" - "0000000000 65535 f\r\n" - "0000000017 00000 n\r\n" - "0000000066 00000 n\r\n" - "0000000122 00000 n\r\n" - "0000000192 00000 n\r\n" - "0000000311 00000 n\r\n" - "trailer\r\n" - "<<\r\n" - "/Root 1 0 R\r\n" - "/Info 3 0 R\r\n" - "/Size 6/ID\\[<.*><.*>\\]>>\r\n" - "startxref\r\n" - "354\r\n" - "%%EOF\r\n"; - -} // namespace - -TEST_F(FPDFEditEmbeddertest, EmptyCreation) { - EXPECT_TRUE(CreateEmptyDocument()); - FPDF_PAGE page = FPDFPage_New(document(), 0, 640.0, 480.0); - EXPECT_NE(nullptr, page); - // The FPDFPage_GenerateContent call should do nothing. - EXPECT_TRUE(FPDFPage_GenerateContent(page)); - EXPECT_TRUE(FPDF_SaveAsCopy(document(), this, 0)); - - EXPECT_THAT(GetString(), testing::MatchesRegex(std::string( - kExpectedPDF, sizeof(kExpectedPDF)))); - FPDF_ClosePage(page); -} - -// Regression test for https://crbug.com/667012 -TEST_F(FPDFEditEmbeddertest, RasterizePDF) { - const char kAllBlackMd5sum[] = "5708fc5c4a8bd0abde99c8e8f0390615"; - - // Get the bitmap for the original document/ - std::unique_ptr orig_bitmap; - { - EXPECT_TRUE(OpenDocument("black.pdf")); - FPDF_PAGE orig_page = LoadPage(0); - ASSERT_TRUE(orig_page); - orig_bitmap = RenderLoadedPage(orig_page); - CompareBitmap(orig_bitmap.get(), 612, 792, kAllBlackMd5sum); - UnloadPage(orig_page); - } - - // Create a new document from |orig_bitmap| and save it. - { - FPDF_DOCUMENT temp_doc = FPDF_CreateNewDocument(); - FPDF_PAGE temp_page = FPDFPage_New(temp_doc, 0, 612, 792); - - // Add the bitmap to an image object and add the image object to the output - // page. - FPDF_PAGEOBJECT temp_img = FPDFPageObj_NewImageObj(temp_doc); - EXPECT_TRUE( - FPDFImageObj_SetBitmap(&temp_page, 1, temp_img, orig_bitmap.get())); - EXPECT_TRUE(FPDFImageObj_SetMatrix(temp_img, 612, 0, 0, 792, 0, 0)); - FPDFPage_InsertObject(temp_page, temp_img); - EXPECT_TRUE(FPDFPage_GenerateContent(temp_page)); - EXPECT_TRUE(FPDF_SaveAsCopy(temp_doc, this, 0)); - FPDF_ClosePage(temp_page); - FPDF_CloseDocument(temp_doc); - } - - // Get the generated content. Make sure it is at least as big as the original - // PDF. - EXPECT_GT(GetString().size(), 923U); - VerifySavedDocument(612, 792, kAllBlackMd5sum); -} - -TEST_F(FPDFEditEmbeddertest, AddPaths) { - // Start with a blank page - FPDF_PAGE page = FPDFPage_New(CreateNewDocument(), 0, 612, 792); - ASSERT_TRUE(page); - - // We will first add a red rectangle - FPDF_PAGEOBJECT red_rect = FPDFPageObj_CreateNewRect(10, 10, 20, 20); - ASSERT_TRUE(red_rect); - // Expect false when trying to set colors out of range - EXPECT_FALSE(FPDFPath_SetStrokeColor(red_rect, 100, 100, 100, 300)); - EXPECT_FALSE(FPDFPath_SetFillColor(red_rect, 200, 256, 200, 0)); - - // Fill rectangle with red and insert to the page - EXPECT_TRUE(FPDFPath_SetFillColor(red_rect, 255, 0, 0, 255)); - EXPECT_TRUE(FPDFPath_SetDrawMode(red_rect, FPDF_FILLMODE_ALTERNATE, 0)); - FPDFPage_InsertObject(page, red_rect); - { - std::unique_ptr page_bitmap = - RenderPageWithFlags(page, nullptr, 0); - CompareBitmap(page_bitmap.get(), 612, 792, - "66d02eaa6181e2c069ce2ea99beda497"); - } - - // Now add to that a green rectangle with some medium alpha - FPDF_PAGEOBJECT green_rect = FPDFPageObj_CreateNewRect(100, 100, 40, 40); - EXPECT_TRUE(FPDFPath_SetFillColor(green_rect, 0, 255, 0, 128)); - - // Make sure the type of the rectangle is a path. - EXPECT_EQ(FPDF_PAGEOBJ_PATH, FPDFPageObj_GetType(green_rect)); - - // Make sure we get back the same color we set previously. - unsigned int R; - unsigned int G; - unsigned int B; - unsigned int A; - EXPECT_TRUE(FPDFPath_GetFillColor(green_rect, &R, &G, &B, &A)); - EXPECT_EQ(0U, R); - EXPECT_EQ(255U, G); - EXPECT_EQ(0U, B); - EXPECT_EQ(128U, A); - - // Make sure the path has 5 points (1 FXPT_TYPE::MoveTo and 4 - // FXPT_TYPE::LineTo). - ASSERT_EQ(5, FPDFPath_CountSegments(green_rect)); - // Verify actual coordinates. - FPDF_PATHSEGMENT segment = FPDFPath_GetPathSegment(green_rect, 0); - float x; - float y; - EXPECT_TRUE(FPDFPathSegment_GetPoint(segment, &x, &y)); - EXPECT_EQ(100, x); - EXPECT_EQ(100, y); - EXPECT_EQ(FPDF_SEGMENT_MOVETO, FPDFPathSegment_GetType(segment)); - EXPECT_FALSE(FPDFPathSegment_GetClose(segment)); - segment = FPDFPath_GetPathSegment(green_rect, 1); - EXPECT_TRUE(FPDFPathSegment_GetPoint(segment, &x, &y)); - EXPECT_EQ(100, x); - EXPECT_EQ(140, y); - EXPECT_EQ(FPDF_SEGMENT_LINETO, FPDFPathSegment_GetType(segment)); - EXPECT_FALSE(FPDFPathSegment_GetClose(segment)); - segment = FPDFPath_GetPathSegment(green_rect, 2); - EXPECT_TRUE(FPDFPathSegment_GetPoint(segment, &x, &y)); - EXPECT_EQ(140, x); - EXPECT_EQ(140, y); - EXPECT_EQ(FPDF_SEGMENT_LINETO, FPDFPathSegment_GetType(segment)); - EXPECT_FALSE(FPDFPathSegment_GetClose(segment)); - segment = FPDFPath_GetPathSegment(green_rect, 3); - EXPECT_TRUE(FPDFPathSegment_GetPoint(segment, &x, &y)); - EXPECT_EQ(140, x); - EXPECT_EQ(100, y); - EXPECT_EQ(FPDF_SEGMENT_LINETO, FPDFPathSegment_GetType(segment)); - EXPECT_FALSE(FPDFPathSegment_GetClose(segment)); - segment = FPDFPath_GetPathSegment(green_rect, 4); - EXPECT_TRUE(FPDFPathSegment_GetPoint(segment, &x, &y)); - EXPECT_EQ(100, x); - EXPECT_EQ(100, y); - EXPECT_EQ(FPDF_SEGMENT_LINETO, FPDFPathSegment_GetType(segment)); - EXPECT_TRUE(FPDFPathSegment_GetClose(segment)); - - EXPECT_TRUE(FPDFPath_SetDrawMode(green_rect, FPDF_FILLMODE_WINDING, 0)); - FPDFPage_InsertObject(page, green_rect); - { - std::unique_ptr page_bitmap = - RenderPageWithFlags(page, nullptr, 0); - CompareBitmap(page_bitmap.get(), 612, 792, - "7b0b87604594e773add528fae567a558"); - } - - // Add a black triangle. - FPDF_PAGEOBJECT black_path = FPDFPageObj_CreateNewPath(400, 100); - EXPECT_TRUE(FPDFPath_SetFillColor(black_path, 0, 0, 0, 200)); - EXPECT_TRUE(FPDFPath_SetDrawMode(black_path, FPDF_FILLMODE_ALTERNATE, 0)); - EXPECT_TRUE(FPDFPath_LineTo(black_path, 400, 200)); - EXPECT_TRUE(FPDFPath_LineTo(black_path, 300, 100)); - EXPECT_TRUE(FPDFPath_Close(black_path)); - - // Make sure the path has 3 points (1 FXPT_TYPE::MoveTo and 2 - // FXPT_TYPE::LineTo). - ASSERT_EQ(3, FPDFPath_CountSegments(black_path)); - // Verify actual coordinates. - segment = FPDFPath_GetPathSegment(black_path, 0); - EXPECT_TRUE(FPDFPathSegment_GetPoint(segment, &x, &y)); - EXPECT_EQ(400, x); - EXPECT_EQ(100, y); - EXPECT_EQ(FPDF_SEGMENT_MOVETO, FPDFPathSegment_GetType(segment)); - EXPECT_FALSE(FPDFPathSegment_GetClose(segment)); - segment = FPDFPath_GetPathSegment(black_path, 1); - EXPECT_TRUE(FPDFPathSegment_GetPoint(segment, &x, &y)); - EXPECT_EQ(400, x); - EXPECT_EQ(200, y); - EXPECT_EQ(FPDF_SEGMENT_LINETO, FPDFPathSegment_GetType(segment)); - EXPECT_FALSE(FPDFPathSegment_GetClose(segment)); - segment = FPDFPath_GetPathSegment(black_path, 2); - EXPECT_TRUE(FPDFPathSegment_GetPoint(segment, &x, &y)); - EXPECT_EQ(300, x); - EXPECT_EQ(100, y); - EXPECT_EQ(FPDF_SEGMENT_LINETO, FPDFPathSegment_GetType(segment)); - EXPECT_TRUE(FPDFPathSegment_GetClose(segment)); - // Make sure out of bounds index access fails properly. - EXPECT_EQ(nullptr, FPDFPath_GetPathSegment(black_path, 3)); - - FPDFPage_InsertObject(page, black_path); - { - std::unique_ptr page_bitmap = - RenderPageWithFlags(page, nullptr, 0); - CompareBitmap(page_bitmap.get(), 612, 792, - "eadc8020a14dfcf091da2688733d8806"); - } - - // Now add a more complex blue path. - FPDF_PAGEOBJECT blue_path = FPDFPageObj_CreateNewPath(200, 200); - EXPECT_TRUE(FPDFPath_SetFillColor(blue_path, 0, 0, 255, 255)); - EXPECT_TRUE(FPDFPath_SetDrawMode(blue_path, FPDF_FILLMODE_WINDING, 0)); - EXPECT_TRUE(FPDFPath_LineTo(blue_path, 230, 230)); - EXPECT_TRUE(FPDFPath_BezierTo(blue_path, 250, 250, 280, 280, 300, 300)); - EXPECT_TRUE(FPDFPath_LineTo(blue_path, 325, 325)); - EXPECT_TRUE(FPDFPath_LineTo(blue_path, 350, 325)); - EXPECT_TRUE(FPDFPath_BezierTo(blue_path, 375, 330, 390, 360, 400, 400)); - EXPECT_TRUE(FPDFPath_Close(blue_path)); - FPDFPage_InsertObject(page, blue_path); - const char kLastMD5[] = "9823e1a21bd9b72b6a442ba4f12af946"; - { - std::unique_ptr page_bitmap = - RenderPageWithFlags(page, nullptr, 0); - CompareBitmap(page_bitmap.get(), 612, 792, kLastMD5); - } - - // Now save the result, closing the page and document - EXPECT_TRUE(FPDFPage_GenerateContent(page)); - EXPECT_TRUE(FPDF_SaveAsCopy(document(), this, 0)); - FPDF_ClosePage(page); - - // Render the saved result - VerifySavedDocument(612, 792, kLastMD5); -} - -TEST_F(FPDFEditEmbeddertest, RemovePageObject) { - // Load document with some text. - EXPECT_TRUE(OpenDocument("hello_world.pdf")); - FPDF_PAGE page = LoadPage(0); - ASSERT_TRUE(page); - - // Show what the original file looks like. - { -#if _FX_PLATFORM_ == _FX_PLATFORM_APPLE_ - const char kOriginalMD5[] = "b90475ca64d1348c3bf5e2b77ad9187a"; -#elif _FX_PLATFORM_ == _FX_PLATFORM_WINDOWS_ - const char kOriginalMD5[] = "e5a6fa28298db07484cd922f3e210c88"; -#else - const char kOriginalMD5[] = "2baa4c0e1758deba1b9c908e1fbd04ed"; -#endif - std::unique_ptr page_bitmap = - RenderPageWithFlags(page, nullptr, 0); - CompareBitmap(page_bitmap.get(), 200, 200, kOriginalMD5); - } - - // Get the "Hello, world!" text object and remove it. - ASSERT_EQ(2, FPDFPage_CountObjects(page)); - FPDF_PAGEOBJECT page_object = FPDFPage_GetObject(page, 0); - ASSERT_TRUE(page_object); - EXPECT_TRUE(FPDFPage_RemoveObject(page, page_object)); - - // Verify the "Hello, world!" text is gone. - { -#if _FX_PLATFORM_ == _FX_PLATFORM_APPLE_ - const char kRemovedMD5[] = "af760c4702467cb1492a57fb8215efaa"; -#elif _FX_PLATFORM_ == _FX_PLATFORM_WINDOWS_ - const char kRemovedMD5[] = "72be917349bf7004a5c39661fe1fc433"; -#else - const char kRemovedMD5[] = "b76df015fe88009c3c342395df96abf1"; -#endif - std::unique_ptr page_bitmap = - RenderPageWithFlags(page, nullptr, 0); - CompareBitmap(page_bitmap.get(), 200, 200, kRemovedMD5); - } - ASSERT_EQ(1, FPDFPage_CountObjects(page)); - - UnloadPage(page); - FPDFPageObj_Destroy(page_object); -} - -TEST_F(FPDFEditEmbeddertest, RemoveMarkedObjectsPrime) { - // Load document with some text. - EXPECT_TRUE(OpenDocument("text_in_page_marked.pdf")); - FPDF_PAGE page = LoadPage(0); - ASSERT_TRUE(page); - - // Show what the original file looks like. - { -#if _FX_PLATFORM_ == _FX_PLATFORM_APPLE_ - const char kOriginalMD5[] = "5a5eb63cb21cc15084fea1f14284b8df"; -#elif _FX_PLATFORM_ == _FX_PLATFORM_WINDOWS_ - const char kOriginalMD5[] = "587c507a40f613f9c530b2ce2d58d655"; -#else - const char kOriginalMD5[] = "2edc6e70d54889aa0c0b7bdf3e168f86"; -#endif - std::unique_ptr page_bitmap = - RenderPageWithFlags(page, nullptr, 0); - CompareBitmap(page_bitmap.get(), 200, 200, kOriginalMD5); - } - - // Iterate over all objects, counting the number of times each content mark - // name appears. - int object_count = FPDFPage_CountObjects(page); - ASSERT_EQ(19, object_count); - - unsigned long prime_count = 0; - unsigned long square_count = 0; - unsigned long greater_than_ten_count = 0; - std::vector primes; - for (int i = 0; i < object_count; ++i) { - FPDF_PAGEOBJECT page_object = FPDFPage_GetObject(page, i); - - int mark_count = FPDFPageObj_CountMarks(page_object); - for (int j = 0; j < mark_count; ++j) { - FPDF_PAGEOBJECTMARK mark = FPDFPageObj_GetMark(page_object, j); - - char buffer[256]; - ASSERT_GT(FPDFPageObjMark_GetName(mark, buffer, 256), 0u); - std::wstring name = - GetPlatformWString(reinterpret_cast(buffer)); - if (name == L"Prime") { - prime_count++; - primes.push_back(page_object); - } else if (name == L"Square") { - square_count++; - } else if (name == L"GreaterThanTen") { - greater_than_ten_count++; - } else { - FAIL(); - } - } - } - - // Expect certain number of tagged objects. The test file contains strings - // from 1 to 19. - EXPECT_EQ(8u, prime_count); - EXPECT_EQ(4u, square_count); - EXPECT_EQ(9u, greater_than_ten_count); - - // Remove all objects marked with "Prime". - for (FPDF_PAGEOBJECT page_object : primes) { - EXPECT_TRUE(FPDFPage_RemoveObject(page, page_object)); - FPDFPageObj_Destroy(page_object); - } - - EXPECT_EQ(11, FPDFPage_CountObjects(page)); - - { -#if _FX_PLATFORM_ == _FX_PLATFORM_APPLE_ - const char kNonPrimesMD5[] = "57e76dc7375d896704f0fd6d6d1b9e65"; -#elif _FX_PLATFORM_ == _FX_PLATFORM_WINDOWS_ - const char kNonPrimesMD5[] = "4d906b57fba36c70c600cf50d60f508c"; -#else - const char kNonPrimesMD5[] = "33d9c45bec41ead92a295e252f6b7922"; -#endif - std::unique_ptr page_bitmap = - RenderPageWithFlags(page, nullptr, 0); - CompareBitmap(page_bitmap.get(), 200, 200, kNonPrimesMD5); - } - - UnloadPage(page); -} - -TEST_F(FPDFEditEmbeddertest, AddAndRemovePaths) { - // Start with a blank page. - FPDF_PAGE page = FPDFPage_New(CreateNewDocument(), 0, 612, 792); - ASSERT_TRUE(page); - - // Render the blank page and verify it's a blank bitmap. - const char kBlankMD5[] = "1940568c9ba33bac5d0b1ee9558c76b3"; - { - std::unique_ptr page_bitmap = - RenderPageWithFlags(page, nullptr, 0); - CompareBitmap(page_bitmap.get(), 612, 792, kBlankMD5); - } - ASSERT_EQ(0, FPDFPage_CountObjects(page)); - - // Add a red rectangle. - FPDF_PAGEOBJECT red_rect = FPDFPageObj_CreateNewRect(10, 10, 20, 20); - ASSERT_TRUE(red_rect); - EXPECT_TRUE(FPDFPath_SetFillColor(red_rect, 255, 0, 0, 255)); - EXPECT_TRUE(FPDFPath_SetDrawMode(red_rect, FPDF_FILLMODE_ALTERNATE, 0)); - FPDFPage_InsertObject(page, red_rect); - const char kRedRectangleMD5[] = "66d02eaa6181e2c069ce2ea99beda497"; - { - std::unique_ptr page_bitmap = - RenderPageWithFlags(page, nullptr, 0); - CompareBitmap(page_bitmap.get(), 612, 792, kRedRectangleMD5); - } - EXPECT_EQ(1, FPDFPage_CountObjects(page)); - - // Remove rectangle and verify it does not render anymore and the bitmap is - // back to a blank one. - EXPECT_TRUE(FPDFPage_RemoveObject(page, red_rect)); - { - std::unique_ptr page_bitmap = - RenderPageWithFlags(page, nullptr, 0); - CompareBitmap(page_bitmap.get(), 612, 792, kBlankMD5); - } - EXPECT_EQ(0, FPDFPage_CountObjects(page)); - - // Trying to remove an object not in the page should return false. - EXPECT_FALSE(FPDFPage_RemoveObject(page, red_rect)); - - FPDF_ClosePage(page); - FPDFPageObj_Destroy(red_rect); -} - -TEST_F(FPDFEditEmbeddertest, PathsPoints) { - CreateNewDocument(); - FPDF_PAGEOBJECT img = FPDFPageObj_NewImageObj(document_); - // This should fail gracefully, even if img is not a path. - ASSERT_EQ(-1, FPDFPath_CountSegments(img)); - - // This should fail gracefully, even if path is NULL. - ASSERT_EQ(-1, FPDFPath_CountSegments(nullptr)); - - // FPDFPath_GetPathSegment() with a non-path. - ASSERT_EQ(nullptr, FPDFPath_GetPathSegment(img, 0)); - // FPDFPath_GetPathSegment() with a NULL path. - ASSERT_EQ(nullptr, FPDFPath_GetPathSegment(nullptr, 0)); - float x; - float y; - // FPDFPathSegment_GetPoint() with a NULL segment. - EXPECT_FALSE(FPDFPathSegment_GetPoint(nullptr, &x, &y)); - - // FPDFPathSegment_GetType() with a NULL segment. - ASSERT_EQ(FPDF_SEGMENT_UNKNOWN, FPDFPathSegment_GetType(nullptr)); - - // FPDFPathSegment_GetClose() with a NULL segment. - EXPECT_FALSE(FPDFPathSegment_GetClose(nullptr)); - - FPDFPageObj_Destroy(img); -} - -TEST_F(FPDFEditEmbeddertest, PathOnTopOfText) { - // Load document with some text - EXPECT_TRUE(OpenDocument("hello_world.pdf")); - FPDF_PAGE page = LoadPage(0); - ASSERT_TRUE(page); - - // Add an opaque rectangle on top of some of the text. - FPDF_PAGEOBJECT red_rect = FPDFPageObj_CreateNewRect(20, 100, 50, 50); - EXPECT_TRUE(FPDFPath_SetFillColor(red_rect, 255, 0, 0, 255)); - EXPECT_TRUE(FPDFPath_SetDrawMode(red_rect, FPDF_FILLMODE_ALTERNATE, 0)); - FPDFPage_InsertObject(page, red_rect); - - // Add a transparent triangle on top of other part of the text. - FPDF_PAGEOBJECT black_path = FPDFPageObj_CreateNewPath(20, 50); - EXPECT_TRUE(FPDFPath_SetFillColor(black_path, 0, 0, 0, 100)); - EXPECT_TRUE(FPDFPath_SetDrawMode(black_path, FPDF_FILLMODE_ALTERNATE, 0)); - EXPECT_TRUE(FPDFPath_LineTo(black_path, 30, 80)); - EXPECT_TRUE(FPDFPath_LineTo(black_path, 40, 10)); - EXPECT_TRUE(FPDFPath_Close(black_path)); - FPDFPage_InsertObject(page, black_path); - - // Render and check the result. Text is slightly different on Mac. - std::unique_ptr bitmap = RenderLoadedPage(page); -#if _FX_PLATFORM_ == _FX_PLATFORM_APPLE_ - const char md5[] = "f9e6fa74230f234286bfcada9f7606d8"; -#else - const char md5[] = "aa71b09b93b55f467f1290e5111babee"; -#endif - CompareBitmap(bitmap.get(), 200, 200, md5); - UnloadPage(page); -} - -TEST_F(FPDFEditEmbeddertest, EditOverExistingContent) { - // Load document with existing content - EXPECT_TRUE(OpenDocument("bug_717.pdf")); - FPDF_PAGE page = LoadPage(0); - ASSERT_TRUE(page); - - // Add a transparent rectangle on top of the existing content - FPDF_PAGEOBJECT red_rect2 = FPDFPageObj_CreateNewRect(90, 700, 25, 50); - EXPECT_TRUE(FPDFPath_SetFillColor(red_rect2, 255, 0, 0, 100)); - EXPECT_TRUE(FPDFPath_SetDrawMode(red_rect2, FPDF_FILLMODE_ALTERNATE, 0)); - FPDFPage_InsertObject(page, red_rect2); - - // Add an opaque rectangle on top of the existing content - FPDF_PAGEOBJECT red_rect = FPDFPageObj_CreateNewRect(115, 700, 25, 50); - EXPECT_TRUE(FPDFPath_SetFillColor(red_rect, 255, 0, 0, 255)); - EXPECT_TRUE(FPDFPath_SetDrawMode(red_rect, FPDF_FILLMODE_ALTERNATE, 0)); - FPDFPage_InsertObject(page, red_rect); - - std::unique_ptr bitmap = RenderLoadedPage(page); - CompareBitmap(bitmap.get(), 612, 792, "ad04e5bd0f471a9a564fb034bd0fb073"); - EXPECT_TRUE(FPDFPage_GenerateContent(page)); - - // Now save the result, closing the page and document - EXPECT_TRUE(FPDF_SaveAsCopy(document(), this, 0)); - UnloadPage(page); - - OpenSavedDocument(); - FPDF_PAGE saved_page = LoadSavedPage(0); - VerifySavedRendering(saved_page, 612, 792, - "ad04e5bd0f471a9a564fb034bd0fb073"); - - ClearString(); - // Add another opaque rectangle on top of the existing content - FPDF_PAGEOBJECT green_rect = FPDFPageObj_CreateNewRect(150, 700, 25, 50); - EXPECT_TRUE(FPDFPath_SetFillColor(green_rect, 0, 255, 0, 255)); - EXPECT_TRUE(FPDFPath_SetDrawMode(green_rect, FPDF_FILLMODE_ALTERNATE, 0)); - FPDFPage_InsertObject(saved_page, green_rect); - - // Add another transparent rectangle on top of existing content - FPDF_PAGEOBJECT green_rect2 = FPDFPageObj_CreateNewRect(175, 700, 25, 50); - EXPECT_TRUE(FPDFPath_SetFillColor(green_rect2, 0, 255, 0, 100)); - EXPECT_TRUE(FPDFPath_SetDrawMode(green_rect2, FPDF_FILLMODE_ALTERNATE, 0)); - FPDFPage_InsertObject(saved_page, green_rect2); - const char kLastMD5[] = "4b5b00f824620f8c9b8801ebb98e1cdd"; - { - std::unique_ptr new_bitmap = - RenderSavedPage(saved_page); - CompareBitmap(new_bitmap.get(), 612, 792, kLastMD5); - } - EXPECT_TRUE(FPDFPage_GenerateContent(saved_page)); - - // Now save the result, closing the page and document - EXPECT_TRUE(FPDF_SaveAsCopy(saved_document_, this, 0)); - - CloseSavedPage(saved_page); - CloseSavedDocument(); - - // Render the saved result - VerifySavedDocument(612, 792, kLastMD5); -} - -TEST_F(FPDFEditEmbeddertest, AddStrokedPaths) { - // Start with a blank page - FPDF_PAGE page = FPDFPage_New(CreateNewDocument(), 0, 612, 792); - - // Add a large stroked rectangle (fill color should not affect it). - FPDF_PAGEOBJECT rect = FPDFPageObj_CreateNewRect(20, 20, 200, 400); - EXPECT_TRUE(FPDFPath_SetFillColor(rect, 255, 0, 0, 255)); - EXPECT_TRUE(FPDFPath_SetStrokeColor(rect, 0, 255, 0, 255)); - EXPECT_TRUE(FPDFPath_SetStrokeWidth(rect, 15.0f)); - EXPECT_TRUE(FPDFPath_SetDrawMode(rect, 0, 1)); - FPDFPage_InsertObject(page, rect); - { - std::unique_ptr page_bitmap = - RenderPageWithFlags(page, nullptr, 0); - CompareBitmap(page_bitmap.get(), 612, 792, - "64bd31f862a89e0a9e505a5af6efd506"); - } - - // Add crossed-checkmark - FPDF_PAGEOBJECT check = FPDFPageObj_CreateNewPath(300, 500); - EXPECT_TRUE(FPDFPath_LineTo(check, 400, 400)); - EXPECT_TRUE(FPDFPath_LineTo(check, 600, 600)); - EXPECT_TRUE(FPDFPath_MoveTo(check, 400, 600)); - EXPECT_TRUE(FPDFPath_LineTo(check, 600, 400)); - EXPECT_TRUE(FPDFPath_SetStrokeColor(check, 128, 128, 128, 180)); - EXPECT_TRUE(FPDFPath_SetStrokeWidth(check, 8.35f)); - EXPECT_TRUE(FPDFPath_SetDrawMode(check, 0, 1)); - FPDFPage_InsertObject(page, check); - { - std::unique_ptr page_bitmap = - RenderPageWithFlags(page, nullptr, 0); - CompareBitmap(page_bitmap.get(), 612, 792, - "4b6f3b9d25c4e194821217d5016c3724"); - } - - // Add stroked and filled oval-ish path. - FPDF_PAGEOBJECT path = FPDFPageObj_CreateNewPath(250, 100); - EXPECT_TRUE(FPDFPath_BezierTo(path, 180, 166, 180, 233, 250, 300)); - EXPECT_TRUE(FPDFPath_LineTo(path, 255, 305)); - EXPECT_TRUE(FPDFPath_BezierTo(path, 325, 233, 325, 166, 255, 105)); - EXPECT_TRUE(FPDFPath_Close(path)); - EXPECT_TRUE(FPDFPath_SetFillColor(path, 200, 128, 128, 100)); - EXPECT_TRUE(FPDFPath_SetStrokeColor(path, 128, 200, 128, 150)); - EXPECT_TRUE(FPDFPath_SetStrokeWidth(path, 10.5f)); - EXPECT_TRUE(FPDFPath_SetDrawMode(path, FPDF_FILLMODE_ALTERNATE, 1)); - FPDFPage_InsertObject(page, path); - { - std::unique_ptr page_bitmap = - RenderPageWithFlags(page, nullptr, 0); - CompareBitmap(page_bitmap.get(), 612, 792, - "ff3e6a22326754944cc6e56609acd73b"); - } - FPDF_ClosePage(page); -} - -TEST_F(FPDFEditEmbeddertest, AddStandardFontText) { - // Start with a blank page - FPDF_PAGE page = FPDFPage_New(CreateNewDocument(), 0, 612, 792); - - // Add some text to the page - FPDF_PAGEOBJECT text_object1 = - FPDFPageObj_NewTextObj(document(), "Arial", 12.0f); - EXPECT_TRUE(text_object1); - std::unique_ptr text1 = - GetFPDFWideString(L"I'm at the bottom of the page"); - EXPECT_TRUE(FPDFText_SetText(text_object1, text1.get())); - FPDFPageObj_Transform(text_object1, 1, 0, 0, 1, 20, 20); - FPDFPage_InsertObject(page, text_object1); - { - std::unique_ptr page_bitmap = - RenderPageWithFlags(page, nullptr, 0); -#if _FX_PLATFORM_ == _FX_PLATFORM_APPLE_ - const char md5[] = "a4dddc1a3930fa694bbff9789dab4161"; -#else - const char md5[] = "eacaa24573b8ce997b3882595f096f00"; -#endif - CompareBitmap(page_bitmap.get(), 612, 792, md5); - } - - // Try another font - FPDF_PAGEOBJECT text_object2 = - FPDFPageObj_NewTextObj(document(), "TimesNewRomanBold", 15.0f); - EXPECT_TRUE(text_object2); - std::unique_ptr text2 = - GetFPDFWideString(L"Hi, I'm Bold. Times New Roman Bold."); - EXPECT_TRUE(FPDFText_SetText(text_object2, text2.get())); - FPDFPageObj_Transform(text_object2, 1, 0, 0, 1, 100, 600); - FPDFPage_InsertObject(page, text_object2); - { - std::unique_ptr page_bitmap = - RenderPageWithFlags(page, nullptr, 0); -#if _FX_PLATFORM_ == _FX_PLATFORM_APPLE_ - const char md5_2[] = "a5c4ace4c6f27644094813fe1441a21c"; -#elif _FX_PLATFORM_ == _FX_PLATFORM_WINDOWS_ - const char md5_2[] = "2587eac9a787e97a37636d54d11bd28d"; -#else - const char md5_2[] = "76fcc7d08aa15445efd2e2ceb7c6cc3b"; -#endif - CompareBitmap(page_bitmap.get(), 612, 792, md5_2); - } - - // And some randomly transformed text - FPDF_PAGEOBJECT text_object3 = - FPDFPageObj_NewTextObj(document(), "Courier-Bold", 20.0f); - EXPECT_TRUE(text_object3); - std::unique_ptr text3 = - GetFPDFWideString(L"Can you read me? <:)>"); - EXPECT_TRUE(FPDFText_SetText(text_object3, text3.get())); - FPDFPageObj_Transform(text_object3, 1, 1.5, 2, 0.5, 200, 200); - FPDFPage_InsertObject(page, text_object3); - { - std::unique_ptr page_bitmap = - RenderPageWithFlags(page, nullptr, 0); -#if _FX_PLATFORM_ == _FX_PLATFORM_APPLE_ - const char md5_3[] = "40b3ef04f915ff4c4208948001763544"; -#elif _FX_PLATFORM_ == _FX_PLATFORM_WINDOWS_ - const char md5_3[] = "7cb61ec112cf400b489360d443ffc9d2"; -#else - const char md5_3[] = "b8a21668f1dab625af7c072e07fcefc4"; -#endif - CompareBitmap(page_bitmap.get(), 612, 792, md5_3); - } - - // TODO(npm): Why are there issues with text rotated by 90 degrees? - // TODO(npm): FPDF_SaveAsCopy not giving the desired result after this. - FPDF_ClosePage(page); -} - -TEST_F(FPDFEditEmbeddertest, GraphicsData) { - // New page - std::unique_ptr page( - FPDFPage_New(CreateNewDocument(), 0, 612, 792)); - - // Create a rect with nontrivial graphics - FPDF_PAGEOBJECT rect1 = FPDFPageObj_CreateNewRect(10, 10, 100, 100); - FPDFPageObj_SetBlendMode(rect1, "Color"); - FPDFPage_InsertObject(page.get(), rect1); - EXPECT_TRUE(FPDFPage_GenerateContent(page.get())); - - // Check that the ExtGState was created - CPDF_Page* cpage = CPDFPageFromFPDFPage(page.get()); - CPDF_Dictionary* graphics_dict = cpage->m_pResources->GetDictFor("ExtGState"); - ASSERT_TRUE(graphics_dict); - EXPECT_EQ(2, static_cast(graphics_dict->GetCount())); - - // Add a text object causing no change to the graphics dictionary - FPDF_PAGEOBJECT text1 = FPDFPageObj_NewTextObj(document(), "Arial", 12.0f); - // Only alpha, the last component, matters for the graphics dictionary. And - // the default value is 255. - EXPECT_TRUE(FPDFText_SetFillColor(text1, 100, 100, 100, 255)); - FPDFPage_InsertObject(page.get(), text1); - EXPECT_TRUE(FPDFPage_GenerateContent(page.get())); - EXPECT_EQ(2, static_cast(graphics_dict->GetCount())); - - // Add a text object increasing the size of the graphics dictionary - FPDF_PAGEOBJECT text2 = - FPDFPageObj_NewTextObj(document(), "Times-Roman", 12.0f); - FPDFPage_InsertObject(page.get(), text2); - FPDFPageObj_SetBlendMode(text2, "Darken"); - EXPECT_TRUE(FPDFText_SetFillColor(text2, 0, 0, 255, 150)); - EXPECT_TRUE(FPDFPage_GenerateContent(page.get())); - EXPECT_EQ(3, static_cast(graphics_dict->GetCount())); - - // Add a path that should reuse graphics - FPDF_PAGEOBJECT path = FPDFPageObj_CreateNewPath(400, 100); - FPDFPageObj_SetBlendMode(path, "Darken"); - EXPECT_TRUE(FPDFPath_SetFillColor(path, 200, 200, 100, 150)); - FPDFPage_InsertObject(page.get(), path); - EXPECT_TRUE(FPDFPage_GenerateContent(page.get())); - EXPECT_EQ(3, static_cast(graphics_dict->GetCount())); - - // Add a rect increasing the size of the graphics dictionary - FPDF_PAGEOBJECT rect2 = FPDFPageObj_CreateNewRect(10, 10, 100, 100); - FPDFPageObj_SetBlendMode(rect2, "Darken"); - EXPECT_TRUE(FPDFPath_SetFillColor(rect2, 0, 0, 255, 150)); - EXPECT_TRUE(FPDFPath_SetStrokeColor(rect2, 0, 0, 0, 200)); - FPDFPage_InsertObject(page.get(), rect2); - EXPECT_TRUE(FPDFPage_GenerateContent(page.get())); - EXPECT_EQ(4, static_cast(graphics_dict->GetCount())); -} - -TEST_F(FPDFEditEmbeddertest, DoubleGenerating) { - // Start with a blank page - FPDF_PAGE page = FPDFPage_New(CreateNewDocument(), 0, 612, 792); - - // Add a red rectangle with some non-default alpha - FPDF_PAGEOBJECT rect = FPDFPageObj_CreateNewRect(10, 10, 100, 100); - EXPECT_TRUE(FPDFPath_SetFillColor(rect, 255, 0, 0, 128)); - EXPECT_TRUE(FPDFPath_SetDrawMode(rect, FPDF_FILLMODE_WINDING, 0)); - FPDFPage_InsertObject(page, rect); - EXPECT_TRUE(FPDFPage_GenerateContent(page)); - - // Check the ExtGState - CPDF_Page* cpage = CPDFPageFromFPDFPage(page); - CPDF_Dictionary* graphics_dict = cpage->m_pResources->GetDictFor("ExtGState"); - ASSERT_TRUE(graphics_dict); - EXPECT_EQ(2, static_cast(graphics_dict->GetCount())); - - // Check the bitmap - { - std::unique_ptr page_bitmap = - RenderPageWithFlags(page, nullptr, 0); - CompareBitmap(page_bitmap.get(), 612, 792, - "5384da3406d62360ffb5cac4476fff1c"); - } - - // Never mind, my new favorite color is blue, increase alpha - EXPECT_TRUE(FPDFPath_SetFillColor(rect, 0, 0, 255, 180)); - EXPECT_TRUE(FPDFPage_GenerateContent(page)); - EXPECT_EQ(3, static_cast(graphics_dict->GetCount())); - - // Check that bitmap displays changed content - { - std::unique_ptr page_bitmap = - RenderPageWithFlags(page, nullptr, 0); - CompareBitmap(page_bitmap.get(), 612, 792, - "2e51656f5073b0bee611d9cd086aa09c"); - } - - // And now generate, without changes - EXPECT_TRUE(FPDFPage_GenerateContent(page)); - EXPECT_EQ(3, static_cast(graphics_dict->GetCount())); - { - std::unique_ptr page_bitmap = - RenderPageWithFlags(page, nullptr, 0); - CompareBitmap(page_bitmap.get(), 612, 792, - "2e51656f5073b0bee611d9cd086aa09c"); - } - - // Add some text to the page - FPDF_PAGEOBJECT text_object = - FPDFPageObj_NewTextObj(document(), "Arial", 12.0f); - std::unique_ptr text = - GetFPDFWideString(L"Something something #text# something"); - EXPECT_TRUE(FPDFText_SetText(text_object, text.get())); - FPDFPageObj_Transform(text_object, 1, 0, 0, 1, 300, 300); - FPDFPage_InsertObject(page, text_object); - EXPECT_TRUE(FPDFPage_GenerateContent(page)); - CPDF_Dictionary* font_dict = cpage->m_pResources->GetDictFor("Font"); - ASSERT_TRUE(font_dict); - EXPECT_EQ(1, static_cast(font_dict->GetCount())); - - // Generate yet again, check dicts are reasonably sized - EXPECT_TRUE(FPDFPage_GenerateContent(page)); - EXPECT_EQ(3, static_cast(graphics_dict->GetCount())); - EXPECT_EQ(1, static_cast(font_dict->GetCount())); - FPDF_ClosePage(page); -} - -TEST_F(FPDFEditEmbeddertest, LoadSimpleType1Font) { - CreateNewDocument(); - // TODO(npm): use other fonts after disallowing loading any font as any type - const CPDF_Font* stock_font = - CPDF_Font::GetStockFont(cpdf_doc(), "Times-Bold"); - const uint8_t* data = stock_font->GetFont()->GetFontData(); - const uint32_t size = stock_font->GetFont()->GetSize(); - std::unique_ptr font( - FPDFText_LoadFont(document(), data, size, FPDF_FONT_TYPE1, false)); - ASSERT_TRUE(font.get()); - CPDF_Font* typed_font = static_cast(font.get()); - EXPECT_TRUE(typed_font->IsType1Font()); - - CPDF_Dictionary* font_dict = typed_font->GetFontDict(); - EXPECT_EQ("Font", font_dict->GetStringFor("Type")); - EXPECT_EQ("Type1", font_dict->GetStringFor("Subtype")); - EXPECT_EQ("Times New Roman Bold", font_dict->GetStringFor("BaseFont")); - ASSERT_TRUE(font_dict->KeyExist("FirstChar")); - ASSERT_TRUE(font_dict->KeyExist("LastChar")); - EXPECT_EQ(32, font_dict->GetIntegerFor("FirstChar")); - EXPECT_EQ(255, font_dict->GetIntegerFor("LastChar")); - - CPDF_Array* widths_array = font_dict->GetArrayFor("Widths"); - ASSERT_TRUE(widths_array); - ASSERT_EQ(224U, widths_array->GetCount()); - EXPECT_EQ(250, widths_array->GetNumberAt(0)); - EXPECT_EQ(569, widths_array->GetNumberAt(11)); - EXPECT_EQ(500, widths_array->GetNumberAt(223)); - CheckFontDescriptor(font_dict, FPDF_FONT_TYPE1, true, false, size, data); -} - -TEST_F(FPDFEditEmbeddertest, LoadSimpleTrueTypeFont) { - CreateNewDocument(); - const CPDF_Font* stock_font = CPDF_Font::GetStockFont(cpdf_doc(), "Courier"); - const uint8_t* data = stock_font->GetFont()->GetFontData(); - const uint32_t size = stock_font->GetFont()->GetSize(); - std::unique_ptr font( - FPDFText_LoadFont(document(), data, size, FPDF_FONT_TRUETYPE, false)); - ASSERT_TRUE(font.get()); - CPDF_Font* typed_font = static_cast(font.get()); - EXPECT_TRUE(typed_font->IsTrueTypeFont()); - - CPDF_Dictionary* font_dict = typed_font->GetFontDict(); - EXPECT_EQ("Font", font_dict->GetStringFor("Type")); - EXPECT_EQ("TrueType", font_dict->GetStringFor("Subtype")); - EXPECT_EQ("Courier New", font_dict->GetStringFor("BaseFont")); - ASSERT_TRUE(font_dict->KeyExist("FirstChar")); - ASSERT_TRUE(font_dict->KeyExist("LastChar")); - EXPECT_EQ(32, font_dict->GetIntegerFor("FirstChar")); - EXPECT_EQ(255, font_dict->GetIntegerFor("LastChar")); - - CPDF_Array* widths_array = font_dict->GetArrayFor("Widths"); - ASSERT_TRUE(widths_array); - ASSERT_EQ(224U, widths_array->GetCount()); - EXPECT_EQ(600, widths_array->GetNumberAt(33)); - EXPECT_EQ(600, widths_array->GetNumberAt(74)); - EXPECT_EQ(600, widths_array->GetNumberAt(223)); - CheckFontDescriptor(font_dict, FPDF_FONT_TRUETYPE, false, false, size, data); -} - -TEST_F(FPDFEditEmbeddertest, LoadCIDType0Font) { - CreateNewDocument(); - const CPDF_Font* stock_font = - CPDF_Font::GetStockFont(cpdf_doc(), "Times-Roman"); - const uint8_t* data = stock_font->GetFont()->GetFontData(); - const uint32_t size = stock_font->GetFont()->GetSize(); - std::unique_ptr font( - FPDFText_LoadFont(document(), data, size, FPDF_FONT_TYPE1, 1)); - ASSERT_TRUE(font.get()); - CPDF_Font* typed_font = static_cast(font.get()); - EXPECT_TRUE(typed_font->IsCIDFont()); - - // Check font dictionary entries - CPDF_Dictionary* font_dict = typed_font->GetFontDict(); - EXPECT_EQ("Font", font_dict->GetStringFor("Type")); - EXPECT_EQ("Type0", font_dict->GetStringFor("Subtype")); - EXPECT_EQ("Times New Roman-Identity-H", font_dict->GetStringFor("BaseFont")); - EXPECT_EQ("Identity-H", font_dict->GetStringFor("Encoding")); - CPDF_Array* descendant_array = font_dict->GetArrayFor("DescendantFonts"); - ASSERT_TRUE(descendant_array); - EXPECT_EQ(1U, descendant_array->GetCount()); - - // Check the CIDFontDict - CPDF_Dictionary* cidfont_dict = descendant_array->GetDictAt(0); - EXPECT_EQ("Font", cidfont_dict->GetStringFor("Type")); - EXPECT_EQ("CIDFontType0", cidfont_dict->GetStringFor("Subtype")); - EXPECT_EQ("Times New Roman", cidfont_dict->GetStringFor("BaseFont")); - CPDF_Dictionary* cidinfo_dict = cidfont_dict->GetDictFor("CIDSystemInfo"); - ASSERT_TRUE(cidinfo_dict); - EXPECT_EQ("Adobe", cidinfo_dict->GetStringFor("Registry")); - EXPECT_EQ("Identity", cidinfo_dict->GetStringFor("Ordering")); - EXPECT_EQ(0, cidinfo_dict->GetNumberFor("Supplement")); - CheckFontDescriptor(cidfont_dict, FPDF_FONT_TYPE1, false, false, size, data); - - // Check widths - CPDF_Array* widths_array = cidfont_dict->GetArrayFor("W"); - ASSERT_TRUE(widths_array); - EXPECT_GT(widths_array->GetCount(), 1U); - CheckCompositeFontWidths(widths_array, typed_font); -} - -TEST_F(FPDFEditEmbeddertest, LoadCIDType2Font) { - CreateNewDocument(); - const CPDF_Font* stock_font = - CPDF_Font::GetStockFont(cpdf_doc(), "Helvetica-Oblique"); - const uint8_t* data = stock_font->GetFont()->GetFontData(); - const uint32_t size = stock_font->GetFont()->GetSize(); - - std::unique_ptr font( - FPDFText_LoadFont(document(), data, size, FPDF_FONT_TRUETYPE, 1)); - ASSERT_TRUE(font.get()); - CPDF_Font* typed_font = static_cast(font.get()); - EXPECT_TRUE(typed_font->IsCIDFont()); - - // Check font dictionary entries - CPDF_Dictionary* font_dict = typed_font->GetFontDict(); - EXPECT_EQ("Font", font_dict->GetStringFor("Type")); - EXPECT_EQ("Type0", font_dict->GetStringFor("Subtype")); - EXPECT_EQ("Arial Italic", font_dict->GetStringFor("BaseFont")); - EXPECT_EQ("Identity-H", font_dict->GetStringFor("Encoding")); - CPDF_Array* descendant_array = font_dict->GetArrayFor("DescendantFonts"); - ASSERT_TRUE(descendant_array); - EXPECT_EQ(1U, descendant_array->GetCount()); - - // Check the CIDFontDict - CPDF_Dictionary* cidfont_dict = descendant_array->GetDictAt(0); - EXPECT_EQ("Font", cidfont_dict->GetStringFor("Type")); - EXPECT_EQ("CIDFontType2", cidfont_dict->GetStringFor("Subtype")); - EXPECT_EQ("Arial Italic", cidfont_dict->GetStringFor("BaseFont")); - CPDF_Dictionary* cidinfo_dict = cidfont_dict->GetDictFor("CIDSystemInfo"); - ASSERT_TRUE(cidinfo_dict); - EXPECT_EQ("Adobe", cidinfo_dict->GetStringFor("Registry")); - EXPECT_EQ("Identity", cidinfo_dict->GetStringFor("Ordering")); - EXPECT_EQ(0, cidinfo_dict->GetNumberFor("Supplement")); - CheckFontDescriptor(cidfont_dict, FPDF_FONT_TRUETYPE, false, true, size, - data); - - // Check widths - CPDF_Array* widths_array = cidfont_dict->GetArrayFor("W"); - ASSERT_TRUE(widths_array); - CheckCompositeFontWidths(widths_array, typed_font); -} - -TEST_F(FPDFEditEmbeddertest, NormalizeNegativeRotation) { - // Load document with a -90 degree rotation - EXPECT_TRUE(OpenDocument("bug_713197.pdf")); - FPDF_PAGE page = LoadPage(0); - EXPECT_NE(nullptr, page); - - EXPECT_EQ(3, FPDFPage_GetRotation(page)); - UnloadPage(page); -} - -TEST_F(FPDFEditEmbeddertest, AddTrueTypeFontText) { - // Start with a blank page - FPDF_PAGE page = FPDFPage_New(CreateNewDocument(), 0, 612, 792); - { - const CPDF_Font* stock_font = CPDF_Font::GetStockFont(cpdf_doc(), "Arial"); - const uint8_t* data = stock_font->GetFont()->GetFontData(); - const uint32_t size = stock_font->GetFont()->GetSize(); - std::unique_ptr font( - FPDFText_LoadFont(document(), data, size, FPDF_FONT_TRUETYPE, 0)); - ASSERT_TRUE(font.get()); - - // Add some text to the page - FPDF_PAGEOBJECT text_object = - FPDFPageObj_CreateTextObj(document(), font.get(), 12.0f); - EXPECT_TRUE(text_object); - std::unique_ptr text = - GetFPDFWideString(L"I am testing my loaded font, WEE."); - EXPECT_TRUE(FPDFText_SetText(text_object, text.get())); - FPDFPageObj_Transform(text_object, 1, 0, 0, 1, 400, 400); - FPDFPage_InsertObject(page, text_object); - std::unique_ptr page_bitmap = - RenderPageWithFlags(page, nullptr, 0); -#if _FX_PLATFORM_ == _FX_PLATFORM_APPLE_ - const char md5[] = "17d2b6cd574cf66170b09c8927529a94"; -#else - const char md5[] = "70592859010ffbf532a2237b8118bcc4"; -#endif // _FX_PLATFORM_ == _FX_PLATFORM_APPLE_ - CompareBitmap(page_bitmap.get(), 612, 792, md5); - - // Add some more text, same font - FPDF_PAGEOBJECT text_object2 = - FPDFPageObj_CreateTextObj(document(), font.get(), 15.0f); - std::unique_ptr text2 = - GetFPDFWideString(L"Bigger font size"); - EXPECT_TRUE(FPDFText_SetText(text_object2, text2.get())); - FPDFPageObj_Transform(text_object2, 1, 0, 0, 1, 200, 200); - FPDFPage_InsertObject(page, text_object2); - } - std::unique_ptr page_bitmap2 = - RenderPageWithFlags(page, nullptr, 0); -#if _FX_PLATFORM_ == _FX_PLATFORM_APPLE_ - const char md5_2[] = "8eded4193ff1f0f77b8b600a825e97ea"; -#else - const char md5_2[] = "c1d10cce1761c4a998a16b2562030568"; -#endif // _FX_PLATFORM_ == _FX_PLATFORM_APPLE_ - CompareBitmap(page_bitmap2.get(), 612, 792, md5_2); - - EXPECT_TRUE(FPDFPage_GenerateContent(page)); - EXPECT_TRUE(FPDF_SaveAsCopy(document(), this, 0)); - FPDF_ClosePage(page); - - VerifySavedDocument(612, 792, md5_2); -} - -TEST_F(FPDFEditEmbeddertest, TransformAnnot) { - // Open a file with one annotation and load its first page. - ASSERT_TRUE(OpenDocument("annotation_highlight_long_content.pdf")); - FPDF_PAGE page = LoadPage(0); - ASSERT_TRUE(page); - - { - // Add an underline annotation to the page without specifying its rectangle. - std::unique_ptr annot( - FPDFPage_CreateAnnot(page, FPDF_ANNOT_UNDERLINE)); - ASSERT_TRUE(annot); - - // FPDFPage_TransformAnnots() should run without errors when modifying - // annotation rectangles. - FPDFPage_TransformAnnots(page, 1, 2, 3, 4, 5, 6); - } - UnloadPage(page); -} - -// TODO(npm): Add tests using Japanese fonts in other OS. -#if _FX_PLATFORM_ == _FX_PLATFORM_LINUX_ -TEST_F(FPDFEditEmbeddertest, AddCIDFontText) { - // Start with a blank page - FPDF_PAGE page = FPDFPage_New(CreateNewDocument(), 0, 612, 792); - CFX_Font CIDfont; - { - // First, get the data from the font - CIDfont.LoadSubst("IPAGothic", 1, 0, 400, 0, 932, 0); - EXPECT_EQ("IPAGothic", CIDfont.GetFaceName()); - const uint8_t* data = CIDfont.GetFontData(); - const uint32_t size = CIDfont.GetSize(); - - // Load the data into a FPDF_Font. - std::unique_ptr font( - FPDFText_LoadFont(document(), data, size, FPDF_FONT_TRUETYPE, 1)); - ASSERT_TRUE(font.get()); - - // Add some text to the page - FPDF_PAGEOBJECT text_object = - FPDFPageObj_CreateTextObj(document(), font.get(), 12.0f); - ASSERT_TRUE(text_object); - std::wstring wstr = L"ABCDEFGhijklmnop."; - std::unique_ptr text = - GetFPDFWideString(wstr); - EXPECT_TRUE(FPDFText_SetText(text_object, text.get())); - FPDFPageObj_Transform(text_object, 1, 0, 0, 1, 200, 200); - FPDFPage_InsertObject(page, text_object); - - // And add some Japanese characters - FPDF_PAGEOBJECT text_object2 = - FPDFPageObj_CreateTextObj(document(), font.get(), 18.0f); - ASSERT_TRUE(text_object2); - std::wstring wstr2 = - L"\u3053\u3093\u306B\u3061\u306f\u4e16\u754C\u3002\u3053\u3053\u306B1" - L"\u756A"; - std::unique_ptr text2 = - GetFPDFWideString(wstr2); - EXPECT_TRUE(FPDFText_SetText(text_object2, text2.get())); - FPDFPageObj_Transform(text_object2, 1, 0, 0, 1, 100, 500); - FPDFPage_InsertObject(page, text_object2); - } - - // Check that the text renders properly. - const char md5[] = "c68cd79aa72bf83a7b25271370d46b21"; - { - std::unique_ptr page_bitmap = - RenderPageWithFlags(page, nullptr, 0); - CompareBitmap(page_bitmap.get(), 612, 792, md5); - } - - // Save the document, close the page. - EXPECT_TRUE(FPDFPage_GenerateContent(page)); - EXPECT_TRUE(FPDF_SaveAsCopy(document(), this, 0)); - FPDF_ClosePage(page); - - VerifySavedDocument(612, 792, md5); -} -#endif // _FX_PLATFORM_ == _FX_PLATFORM_LINUX_ - -TEST_F(FPDFEditEmbeddertest, SaveAndRender) { - const char md5[] = "3c20472b0552c0c22b88ab1ed8c6202b"; - { - EXPECT_TRUE(OpenDocument("bug_779.pdf")); - FPDF_PAGE page = LoadPage(0); - ASSERT_NE(nullptr, page); - - // Now add a more complex blue path. - FPDF_PAGEOBJECT green_path = FPDFPageObj_CreateNewPath(20, 20); - EXPECT_TRUE(FPDFPath_SetFillColor(green_path, 0, 255, 0, 200)); - // TODO(npm): stroking will cause the MD5s to differ. - EXPECT_TRUE(FPDFPath_SetDrawMode(green_path, FPDF_FILLMODE_WINDING, 0)); - EXPECT_TRUE(FPDFPath_LineTo(green_path, 20, 63)); - EXPECT_TRUE(FPDFPath_BezierTo(green_path, 55, 55, 78, 78, 90, 90)); - EXPECT_TRUE(FPDFPath_LineTo(green_path, 133, 133)); - EXPECT_TRUE(FPDFPath_LineTo(green_path, 133, 33)); - EXPECT_TRUE(FPDFPath_BezierTo(green_path, 38, 33, 39, 36, 40, 40)); - EXPECT_TRUE(FPDFPath_Close(green_path)); - FPDFPage_InsertObject(page, green_path); - std::unique_ptr page_bitmap = - RenderLoadedPage(page); - CompareBitmap(page_bitmap.get(), 612, 792, md5); - - // Now save the result, closing the page and document - EXPECT_TRUE(FPDFPage_GenerateContent(page)); - EXPECT_TRUE(FPDF_SaveAsCopy(document(), this, 0)); - UnloadPage(page); - } - - VerifySavedDocument(612, 792, md5); -} - -TEST_F(FPDFEditEmbeddertest, ExtractImageBitmap) { - ASSERT_TRUE(OpenDocument("embedded_images.pdf")); - FPDF_PAGE page = LoadPage(0); - ASSERT_TRUE(page); - ASSERT_EQ(39, FPDFPage_CountObjects(page)); - - FPDF_PAGEOBJECT obj = FPDFPage_GetObject(page, 32); - EXPECT_NE(FPDF_PAGEOBJ_IMAGE, FPDFPageObj_GetType(obj)); - EXPECT_FALSE(FPDFImageObj_GetBitmap(obj)); - - obj = FPDFPage_GetObject(page, 33); - ASSERT_EQ(FPDF_PAGEOBJ_IMAGE, FPDFPageObj_GetType(obj)); - FPDF_BITMAP bitmap = FPDFImageObj_GetBitmap(obj); - EXPECT_EQ(FPDFBitmap_BGR, FPDFBitmap_GetFormat(bitmap)); - CompareBitmap(bitmap, 109, 88, "d65e98d968d196abf13f78aec655ffae"); - FPDFBitmap_Destroy(bitmap); - - obj = FPDFPage_GetObject(page, 34); - ASSERT_EQ(FPDF_PAGEOBJ_IMAGE, FPDFPageObj_GetType(obj)); - bitmap = FPDFImageObj_GetBitmap(obj); - EXPECT_EQ(FPDFBitmap_BGR, FPDFBitmap_GetFormat(bitmap)); - CompareBitmap(bitmap, 103, 75, "1287711c84dbef767c435d11697661d6"); - FPDFBitmap_Destroy(bitmap); - - obj = FPDFPage_GetObject(page, 35); - ASSERT_EQ(FPDF_PAGEOBJ_IMAGE, FPDFPageObj_GetType(obj)); - bitmap = FPDFImageObj_GetBitmap(obj); - EXPECT_EQ(FPDFBitmap_Gray, FPDFBitmap_GetFormat(bitmap)); - CompareBitmap(bitmap, 92, 68, "9c6d76cb1e37ef8514f9455d759391f3"); - FPDFBitmap_Destroy(bitmap); - - obj = FPDFPage_GetObject(page, 36); - ASSERT_EQ(FPDF_PAGEOBJ_IMAGE, FPDFPageObj_GetType(obj)); - bitmap = FPDFImageObj_GetBitmap(obj); - EXPECT_EQ(FPDFBitmap_BGR, FPDFBitmap_GetFormat(bitmap)); - CompareBitmap(bitmap, 79, 60, "15cb6a49a2e354ed0e9f45dd34e3da1a"); - FPDFBitmap_Destroy(bitmap); - - obj = FPDFPage_GetObject(page, 37); - ASSERT_EQ(FPDF_PAGEOBJ_IMAGE, FPDFPageObj_GetType(obj)); - bitmap = FPDFImageObj_GetBitmap(obj); - EXPECT_EQ(FPDFBitmap_BGR, FPDFBitmap_GetFormat(bitmap)); - CompareBitmap(bitmap, 126, 106, "be5a64ba7890d2657522af6524118534"); - FPDFBitmap_Destroy(bitmap); - - obj = FPDFPage_GetObject(page, 38); - ASSERT_EQ(FPDF_PAGEOBJ_IMAGE, FPDFPageObj_GetType(obj)); - bitmap = FPDFImageObj_GetBitmap(obj); - EXPECT_EQ(FPDFBitmap_BGR, FPDFBitmap_GetFormat(bitmap)); - CompareBitmap(bitmap, 194, 119, "f9e24207ee1bc0db6c543d33a5f12ec5"); - FPDFBitmap_Destroy(bitmap); - UnloadPage(page); -} - -TEST_F(FPDFEditEmbeddertest, ExtractJBigImageBitmap) { - ASSERT_TRUE(OpenDocument("bug_631912.pdf")); - FPDF_PAGE page = LoadPage(0); - ASSERT_TRUE(page); - ASSERT_EQ(1, FPDFPage_CountObjects(page)); - - FPDF_PAGEOBJECT obj = FPDFPage_GetObject(page, 0); - ASSERT_EQ(FPDF_PAGEOBJ_IMAGE, FPDFPageObj_GetType(obj)); - { - std::unique_ptr bitmap( - FPDFImageObj_GetBitmap(obj)); - ASSERT_TRUE(bitmap); - EXPECT_EQ(FPDFBitmap_Gray, FPDFBitmap_GetFormat(bitmap.get())); - CompareBitmap(bitmap.get(), 1152, 720, "3f6a48e2b3e91b799bf34567f55cb4de"); - } - - UnloadPage(page); -} - -TEST_F(FPDFEditEmbeddertest, GetImageData) { - EXPECT_TRUE(OpenDocument("embedded_images.pdf")); - FPDF_PAGE page = LoadPage(0); - ASSERT_TRUE(page); - ASSERT_EQ(39, FPDFPage_CountObjects(page)); - - // Retrieve an image object with flate-encoded data stream. - FPDF_PAGEOBJECT obj = FPDFPage_GetObject(page, 33); - ASSERT_EQ(FPDF_PAGEOBJ_IMAGE, FPDFPageObj_GetType(obj)); - - // Check that the raw image data has the correct length and hash value. - unsigned long len = FPDFImageObj_GetImageDataRaw(obj, nullptr, 0); - std::vector buf(len); - EXPECT_EQ(4091u, FPDFImageObj_GetImageDataRaw(obj, buf.data(), len)); - EXPECT_EQ("f73802327d2e88e890f653961bcda81a", - GenerateMD5Base16(reinterpret_cast(buf.data()), len)); - - // Check that the decoded image data has the correct length and hash value. - len = FPDFImageObj_GetImageDataDecoded(obj, nullptr, 0); - buf.clear(); - buf.resize(len); - EXPECT_EQ(28776u, FPDFImageObj_GetImageDataDecoded(obj, buf.data(), len)); - EXPECT_EQ("cb3637934bb3b95a6e4ae1ea9eb9e56e", - GenerateMD5Base16(reinterpret_cast(buf.data()), len)); - - // Retrieve an image obejct with DCTDecode-encoded data stream. - obj = FPDFPage_GetObject(page, 37); - ASSERT_EQ(FPDF_PAGEOBJ_IMAGE, FPDFPageObj_GetType(obj)); - - // Check that the raw image data has the correct length and hash value. - len = FPDFImageObj_GetImageDataRaw(obj, nullptr, 0); - buf.clear(); - buf.resize(len); - EXPECT_EQ(4370u, FPDFImageObj_GetImageDataRaw(obj, buf.data(), len)); - EXPECT_EQ("6aae1f3710335023a9e12191be66b64b", - GenerateMD5Base16(reinterpret_cast(buf.data()), len)); - - // Check that the decoded image data has the correct length and hash value, - // which should be the same as those of the raw data, since this image is - // encoded by a single DCTDecode filter and decoding is a noop. - len = FPDFImageObj_GetImageDataDecoded(obj, nullptr, 0); - buf.clear(); - buf.resize(len); - EXPECT_EQ(4370u, FPDFImageObj_GetImageDataDecoded(obj, buf.data(), len)); - EXPECT_EQ("6aae1f3710335023a9e12191be66b64b", - GenerateMD5Base16(reinterpret_cast(buf.data()), len)); - - UnloadPage(page); -} - -TEST_F(FPDFEditEmbeddertest, DestroyPageObject) { - FPDF_PAGEOBJECT rect = FPDFPageObj_CreateNewRect(10, 10, 20, 20); - ASSERT_TRUE(rect); - - // There should be no memory leaks with a call to FPDFPageObj_Destroy(). - FPDFPageObj_Destroy(rect); -} - -TEST_F(FPDFEditEmbeddertest, GetImageFilters) { - EXPECT_TRUE(OpenDocument("embedded_images.pdf")); - FPDF_PAGE page = LoadPage(0); - ASSERT_TRUE(page); - - // Verify that retrieving the filter of a non-image object would fail. - FPDF_PAGEOBJECT obj = FPDFPage_GetObject(page, 32); - ASSERT_NE(FPDF_PAGEOBJ_IMAGE, FPDFPageObj_GetType(obj)); - ASSERT_EQ(0, FPDFImageObj_GetImageFilterCount(obj)); - EXPECT_EQ(0u, FPDFImageObj_GetImageFilter(obj, 0, nullptr, 0)); - - // Verify the returned filter string for an image object with a single filter. - obj = FPDFPage_GetObject(page, 33); - ASSERT_EQ(FPDF_PAGEOBJ_IMAGE, FPDFPageObj_GetType(obj)); - ASSERT_EQ(1, FPDFImageObj_GetImageFilterCount(obj)); - unsigned long len = FPDFImageObj_GetImageFilter(obj, 0, nullptr, 0); - std::vector buf(len); - static constexpr char kFlateDecode[] = "FlateDecode"; - EXPECT_EQ(sizeof(kFlateDecode), - FPDFImageObj_GetImageFilter(obj, 0, buf.data(), len)); - EXPECT_STREQ(kFlateDecode, buf.data()); - EXPECT_EQ(0u, FPDFImageObj_GetImageFilter(obj, 1, nullptr, 0)); - - // Verify all the filters for an image object with a list of filters. - obj = FPDFPage_GetObject(page, 38); - ASSERT_EQ(FPDF_PAGEOBJ_IMAGE, FPDFPageObj_GetType(obj)); - ASSERT_EQ(2, FPDFImageObj_GetImageFilterCount(obj)); - len = FPDFImageObj_GetImageFilter(obj, 0, nullptr, 0); - buf.clear(); - buf.resize(len); - static constexpr char kASCIIHexDecode[] = "ASCIIHexDecode"; - EXPECT_EQ(sizeof(kASCIIHexDecode), - FPDFImageObj_GetImageFilter(obj, 0, buf.data(), len)); - EXPECT_STREQ(kASCIIHexDecode, buf.data()); - - len = FPDFImageObj_GetImageFilter(obj, 1, nullptr, 0); - buf.clear(); - buf.resize(len); - static constexpr char kDCTDecode[] = "DCTDecode"; - EXPECT_EQ(sizeof(kDCTDecode), - FPDFImageObj_GetImageFilter(obj, 1, buf.data(), len)); - EXPECT_STREQ(kDCTDecode, buf.data()); - - UnloadPage(page); -} - -TEST_F(FPDFEditEmbeddertest, GetImageMetadata) { - ASSERT_TRUE(OpenDocument("embedded_images.pdf")); - FPDF_PAGE page = LoadPage(0); - ASSERT_TRUE(page); - - // Check that getting the metadata of a null object would fail. - FPDF_IMAGEOBJ_METADATA metadata; - EXPECT_FALSE(FPDFImageObj_GetImageMetadata(nullptr, page, &metadata)); - - // Check that receiving the metadata with a null metadata object would fail. - FPDF_PAGEOBJECT obj = FPDFPage_GetObject(page, 35); - EXPECT_FALSE(FPDFImageObj_GetImageMetadata(obj, page, nullptr)); - - // Check that when retrieving an image object's metadata without passing in - // |page|, all values are correct, with the last two being default values. - ASSERT_EQ(FPDF_PAGEOBJ_IMAGE, FPDFPageObj_GetType(obj)); - ASSERT_TRUE(FPDFImageObj_GetImageMetadata(obj, nullptr, &metadata)); - EXPECT_EQ(7, metadata.marked_content_id); - EXPECT_EQ(92u, metadata.width); - EXPECT_EQ(68u, metadata.height); - EXPECT_NEAR(96.000000, metadata.horizontal_dpi, 0.001); - EXPECT_NEAR(96.000000, metadata.vertical_dpi, 0.001); - EXPECT_EQ(0u, metadata.bits_per_pixel); - EXPECT_EQ(FPDF_COLORSPACE_UNKNOWN, metadata.colorspace); - - // Verify the metadata of a bitmap image with indexed colorspace. - ASSERT_TRUE(FPDFImageObj_GetImageMetadata(obj, page, &metadata)); - EXPECT_EQ(7, metadata.marked_content_id); - EXPECT_EQ(92u, metadata.width); - EXPECT_EQ(68u, metadata.height); - EXPECT_NEAR(96.000000, metadata.horizontal_dpi, 0.001); - EXPECT_NEAR(96.000000, metadata.vertical_dpi, 0.001); - EXPECT_EQ(1u, metadata.bits_per_pixel); - EXPECT_EQ(FPDF_COLORSPACE_INDEXED, metadata.colorspace); - - // Verify the metadata of an image with RGB colorspace. - obj = FPDFPage_GetObject(page, 37); - ASSERT_EQ(FPDF_PAGEOBJ_IMAGE, FPDFPageObj_GetType(obj)); - ASSERT_TRUE(FPDFImageObj_GetImageMetadata(obj, page, &metadata)); - EXPECT_EQ(9, metadata.marked_content_id); - EXPECT_EQ(126u, metadata.width); - EXPECT_EQ(106u, metadata.height); - EXPECT_NEAR(162.173752, metadata.horizontal_dpi, 0.001); - EXPECT_NEAR(162.555878, metadata.vertical_dpi, 0.001); - EXPECT_EQ(24u, metadata.bits_per_pixel); - EXPECT_EQ(FPDF_COLORSPACE_DEVICERGB, metadata.colorspace); - - UnloadPage(page); -} diff --git a/fpdfsdk/fpdfeditimg.cpp b/fpdfsdk/fpdfeditimg.cpp deleted file mode 100644 index f1d90ece2d..0000000000 --- a/fpdfsdk/fpdfeditimg.cpp +++ /dev/null @@ -1,310 +0,0 @@ -// Copyright 2014 PDFium Authors. All rights reserved. -// 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_edit.h" - -#include "core/fpdfapi/cpdf_modulemgr.h" -#include "core/fpdfapi/page/cpdf_image.h" -#include "core/fpdfapi/page/cpdf_imageobject.h" -#include "core/fpdfapi/page/cpdf_page.h" -#include "core/fpdfapi/page/cpdf_pageobject.h" -#include "core/fpdfapi/parser/cpdf_array.h" -#include "core/fpdfapi/parser/cpdf_name.h" -#include "core/fpdfapi/render/cpdf_dibsource.h" -#include "fpdfsdk/cpdfsdk_customaccess.h" -#include "fpdfsdk/cpdfsdk_helpers.h" -#include "third_party/base/ptr_util.h" - -namespace { - -// These checks ensure the consistency of colorspace values across core/ and -// public/. -static_assert(PDFCS_DEVICEGRAY == FPDF_COLORSPACE_DEVICEGRAY, - "PDFCS_DEVICEGRAY value mismatch"); -static_assert(PDFCS_DEVICERGB == FPDF_COLORSPACE_DEVICERGB, - "PDFCS_DEVICERGB value mismatch"); -static_assert(PDFCS_DEVICECMYK == FPDF_COLORSPACE_DEVICECMYK, - "PDFCS_DEVICECMYK value mismatch"); -static_assert(PDFCS_CALGRAY == FPDF_COLORSPACE_CALGRAY, - "PDFCS_CALGRAY value mismatch"); -static_assert(PDFCS_CALRGB == FPDF_COLORSPACE_CALRGB, - "PDFCS_CALRGB value mismatch"); -static_assert(PDFCS_LAB == FPDF_COLORSPACE_LAB, "PDFCS_LAB value mismatch"); -static_assert(PDFCS_ICCBASED == FPDF_COLORSPACE_ICCBASED, - "PDFCS_ICCBASED value mismatch"); -static_assert(PDFCS_SEPARATION == FPDF_COLORSPACE_SEPARATION, - "PDFCS_SEPARATION value mismatch"); -static_assert(PDFCS_DEVICEN == FPDF_COLORSPACE_DEVICEN, - "PDFCS_DEVICEN value mismatch"); -static_assert(PDFCS_INDEXED == FPDF_COLORSPACE_INDEXED, - "PDFCS_INDEXED value mismatch"); -static_assert(PDFCS_PATTERN == FPDF_COLORSPACE_PATTERN, - "PDFCS_PATTERN value mismatch"); - -RetainPtr MakeSeekableReadStream( - FPDF_FILEACCESS* pFileAccess) { - return pdfium::MakeRetain(pFileAccess); -} - -bool LoadJpegHelper(FPDF_PAGE* pages, - int nCount, - FPDF_PAGEOBJECT image_object, - FPDF_FILEACCESS* fileAccess, - bool inlineJpeg) { - if (!image_object || !fileAccess) - return false; - - RetainPtr pFile = MakeSeekableReadStream(fileAccess); - CPDF_ImageObject* pImgObj = static_cast(image_object); - - if (pages) { - for (int index = 0; index < nCount; index++) { - CPDF_Page* pPage = CPDFPageFromFPDFPage(pages[index]); - if (pPage) - pImgObj->GetImage()->ResetCache(pPage, nullptr); - } - } - - if (inlineJpeg) - pImgObj->GetImage()->SetJpegImageInline(pFile); - else - pImgObj->GetImage()->SetJpegImage(pFile); - pImgObj->SetDirty(true); - return true; -} - -} // namespace - -FPDF_EXPORT FPDF_PAGEOBJECT FPDF_CALLCONV -FPDFPageObj_NewImageObj(FPDF_DOCUMENT document) { - CPDF_Document* pDoc = CPDFDocumentFromFPDFDocument(document); - if (!pDoc) - return nullptr; - - auto pImageObj = pdfium::MakeUnique(); - pImageObj->SetImage(pdfium::MakeRetain(pDoc)); - return pImageObj.release(); -} - -FPDF_EXPORT FPDF_BOOL FPDF_CALLCONV -FPDFImageObj_LoadJpegFile(FPDF_PAGE* pages, - int nCount, - FPDF_PAGEOBJECT image_object, - FPDF_FILEACCESS* fileAccess) { - return LoadJpegHelper(pages, nCount, image_object, fileAccess, false); -} - -FPDF_EXPORT FPDF_BOOL FPDF_CALLCONV -FPDFImageObj_LoadJpegFileInline(FPDF_PAGE* pages, - int nCount, - FPDF_PAGEOBJECT image_object, - FPDF_FILEACCESS* fileAccess) { - return LoadJpegHelper(pages, nCount, image_object, fileAccess, true); -} - -FPDF_EXPORT FPDF_BOOL FPDF_CALLCONV -FPDFImageObj_SetMatrix(FPDF_PAGEOBJECT image_object, - double a, - double b, - double c, - double d, - double e, - double f) { - if (!image_object) - return false; - - CPDF_ImageObject* pImgObj = static_cast(image_object); - pImgObj->set_matrix(CFX_Matrix(static_cast(a), static_cast(b), - static_cast(c), static_cast(d), - static_cast(e), static_cast(f))); - pImgObj->CalcBoundingBox(); - pImgObj->SetDirty(true); - return true; -} - -FPDF_EXPORT FPDF_BOOL FPDF_CALLCONV -FPDFImageObj_SetBitmap(FPDF_PAGE* pages, - int nCount, - FPDF_PAGEOBJECT image_object, - FPDF_BITMAP bitmap) { - if (!image_object || !bitmap || !pages) - return false; - - CPDF_ImageObject* pImgObj = static_cast(image_object); - for (int index = 0; index < nCount; index++) { - CPDF_Page* pPage = CPDFPageFromFPDFPage(pages[index]); - if (pPage) - pImgObj->GetImage()->ResetCache(pPage, nullptr); - } - RetainPtr holder(CFXBitmapFromFPDFBitmap(bitmap)); - pImgObj->GetImage()->SetImage(holder); - pImgObj->CalcBoundingBox(); - pImgObj->SetDirty(true); - return true; -} - -FPDF_EXPORT FPDF_BITMAP FPDF_CALLCONV -FPDFImageObj_GetBitmap(FPDF_PAGEOBJECT image_object) { - CPDF_PageObject* pObj = CPDFPageObjectFromFPDFPageObject(image_object); - if (!pObj || !pObj->IsImage()) - return nullptr; - - RetainPtr pImg = pObj->AsImage()->GetImage(); - if (!pImg) - return nullptr; - - RetainPtr pSource = pImg->LoadDIBSource(); - if (!pSource) - return nullptr; - - RetainPtr pBitmap; - // If the source image has a representation of 1 bit per pixel, then convert - // it to a grayscale bitmap having 1 byte per pixel, since bitmaps have no - // concept of bits. Otherwise, convert the source image to a bitmap directly, - // retaining its color representation. - if (pSource->GetBPP() == 1) - pBitmap = pSource->CloneConvert(FXDIB_8bppRgb); - else - pBitmap = pSource->Clone(nullptr); - - return pBitmap.Leak(); -} - -FPDF_EXPORT unsigned long FPDF_CALLCONV -FPDFImageObj_GetImageDataDecoded(FPDF_PAGEOBJECT image_object, - void* buffer, - unsigned long buflen) { - CPDF_PageObject* pObj = CPDFPageObjectFromFPDFPageObject(image_object); - if (!pObj || !pObj->IsImage()) - return 0; - - RetainPtr pImg = pObj->AsImage()->GetImage(); - if (!pImg) - return 0; - - CPDF_Stream* pImgStream = pImg->GetStream(); - if (!pImgStream) - return 0; - - return DecodeStreamMaybeCopyAndReturnLength(pImgStream, buffer, buflen); -} - -FPDF_EXPORT unsigned long FPDF_CALLCONV -FPDFImageObj_GetImageDataRaw(FPDF_PAGEOBJECT image_object, - void* buffer, - unsigned long buflen) { - CPDF_PageObject* pObj = CPDFPageObjectFromFPDFPageObject(image_object); - if (!pObj || !pObj->IsImage()) - return 0; - - RetainPtr pImg = pObj->AsImage()->GetImage(); - if (!pImg) - return 0; - - CPDF_Stream* pImgStream = pImg->GetStream(); - if (!pImgStream) - return 0; - - uint32_t len = pImgStream->GetRawSize(); - if (buffer && buflen >= len) - memcpy(buffer, pImgStream->GetRawData(), len); - - return len; -} - -FPDF_EXPORT int FPDF_CALLCONV -FPDFImageObj_GetImageFilterCount(FPDF_PAGEOBJECT image_object) { - CPDF_PageObject* pObj = CPDFPageObjectFromFPDFPageObject(image_object); - if (!pObj || !pObj->IsImage()) - return 0; - - RetainPtr pImg = pObj->AsImage()->GetImage(); - if (!pImg) - return 0; - - CPDF_Dictionary* pDict = pImg->GetDict(); - CPDF_Object* pFilter = pDict ? pDict->GetDirectObjectFor("Filter") : nullptr; - if (!pFilter) - return 0; - - if (pFilter->IsArray()) - return pFilter->AsArray()->GetCount(); - if (pFilter->IsName()) - return 1; - - return 0; -} - -FPDF_EXPORT unsigned long FPDF_CALLCONV -FPDFImageObj_GetImageFilter(FPDF_PAGEOBJECT image_object, - int index, - void* buffer, - unsigned long buflen) { - if (index < 0 || index >= FPDFImageObj_GetImageFilterCount(image_object)) - return 0; - - CPDF_PageObject* pObj = CPDFPageObjectFromFPDFPageObject(image_object); - CPDF_Object* pFilter = - pObj->AsImage()->GetImage()->GetDict()->GetDirectObjectFor("Filter"); - ByteString bsFilter; - if (pFilter->IsName()) - bsFilter = pFilter->AsName()->GetString(); - else - bsFilter = pFilter->AsArray()->GetStringAt(index); - - unsigned long len = bsFilter.GetLength() + 1; - if (buffer && len <= buflen) - memcpy(buffer, bsFilter.c_str(), len); - return len; -} - -FPDF_EXPORT FPDF_BOOL FPDF_CALLCONV -FPDFImageObj_GetImageMetadata(FPDF_PAGEOBJECT image_object, - FPDF_PAGE page, - FPDF_IMAGEOBJ_METADATA* metadata) { - CPDF_PageObject* pObj = CPDFPageObjectFromFPDFPageObject(image_object); - if (!pObj || !pObj->IsImage() || !metadata) - return false; - - RetainPtr pImg = pObj->AsImage()->GetImage(); - if (!pImg) - return false; - - metadata->marked_content_id = pObj->m_ContentMark.GetMarkedContentID(); - - const int nPixelWidth = pImg->GetPixelWidth(); - const int nPixelHeight = pImg->GetPixelHeight(); - metadata->width = nPixelWidth; - metadata->height = nPixelHeight; - - const float nWidth = pObj->m_Right - pObj->m_Left; - const float nHeight = pObj->m_Top - pObj->m_Bottom; - constexpr int nPointsPerInch = 72; - if (nWidth != 0 && nHeight != 0) { - metadata->horizontal_dpi = nPixelWidth / nWidth * nPointsPerInch; - metadata->vertical_dpi = nPixelHeight / nHeight * nPointsPerInch; - } - - metadata->bits_per_pixel = 0; - metadata->colorspace = FPDF_COLORSPACE_UNKNOWN; - - CPDF_Page* pPage = CPDFPageFromFPDFPage(page); - if (!pPage || !pPage->m_pDocument.Get() || !pImg->GetStream()) - return true; - - auto pSource = pdfium::MakeRetain(); - CPDF_DIBSource::LoadState ret = pSource->StartLoadDIBSource( - pPage->m_pDocument.Get(), pImg->GetStream(), false, nullptr, - pPage->m_pPageResources.Get()); - if (ret == CPDF_DIBSource::LoadState::kFail) - return true; - - metadata->bits_per_pixel = pSource->GetBPP(); - if (pSource->GetColorSpace()) - metadata->colorspace = pSource->GetColorSpace()->GetFamily(); - - return true; -} diff --git a/fpdfsdk/fpdfeditimg_unittest.cpp b/fpdfsdk/fpdfeditimg_unittest.cpp deleted file mode 100644 index fcc081ab76..0000000000 --- a/fpdfsdk/fpdfeditimg_unittest.cpp +++ /dev/null @@ -1,71 +0,0 @@ -// Copyright 2016 PDFium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#include "public/fpdf_edit.h" - -#include "core/fpdfapi/cpdf_modulemgr.h" -#include "testing/gtest/include/gtest/gtest.h" - -class PDFEditTest : public testing::Test { - void SetUp() override { CPDF_ModuleMgr::Get()->Init(); } - - void TearDown() override { CPDF_ModuleMgr::Destroy(); } -}; - -TEST_F(PDFEditTest, InsertObjectWithInvalidPage) { - FPDF_DOCUMENT doc = FPDF_CreateNewDocument(); - FPDF_PAGE page = FPDFPage_New(doc, 0, 100, 100); - EXPECT_EQ(0, FPDFPage_CountObjects(page)); - - FPDFPage_InsertObject(nullptr, nullptr); - EXPECT_EQ(0, FPDFPage_CountObjects(page)); - - FPDFPage_InsertObject(page, nullptr); - EXPECT_EQ(0, FPDFPage_CountObjects(page)); - - FPDF_PAGEOBJECT page_image = FPDFPageObj_NewImageObj(doc); - FPDFPage_InsertObject(nullptr, page_image); - EXPECT_EQ(0, FPDFPage_CountObjects(page)); - - FPDF_ClosePage(page); - FPDF_CloseDocument(doc); -} - -TEST_F(PDFEditTest, NewImageObj) { - FPDF_DOCUMENT doc = FPDF_CreateNewDocument(); - FPDF_PAGE page = FPDFPage_New(doc, 0, 100, 100); - EXPECT_EQ(0, FPDFPage_CountObjects(page)); - - FPDF_PAGEOBJECT page_image = FPDFPageObj_NewImageObj(doc); - FPDFPage_InsertObject(page, page_image); - EXPECT_EQ(1, FPDFPage_CountObjects(page)); - EXPECT_TRUE(FPDFPage_GenerateContent(page)); - - FPDF_ClosePage(page); - FPDF_CloseDocument(doc); -} - -TEST_F(PDFEditTest, NewImageObjGenerateContent) { - FPDF_DOCUMENT doc = FPDF_CreateNewDocument(); - FPDF_PAGE page = FPDFPage_New(doc, 0, 100, 100); - EXPECT_EQ(0, FPDFPage_CountObjects(page)); - - constexpr int kBitmapSize = 50; - FPDF_BITMAP bitmap = FPDFBitmap_Create(kBitmapSize, kBitmapSize, 0); - FPDFBitmap_FillRect(bitmap, 0, 0, kBitmapSize, kBitmapSize, 0x00000000); - EXPECT_EQ(kBitmapSize, FPDFBitmap_GetWidth(bitmap)); - EXPECT_EQ(kBitmapSize, FPDFBitmap_GetHeight(bitmap)); - - FPDF_PAGEOBJECT page_image = FPDFPageObj_NewImageObj(doc); - ASSERT_TRUE(FPDFImageObj_SetBitmap(&page, 0, page_image, bitmap)); - ASSERT_TRUE( - FPDFImageObj_SetMatrix(page_image, kBitmapSize, 0, 0, kBitmapSize, 0, 0)); - FPDFPage_InsertObject(page, page_image); - EXPECT_EQ(1, FPDFPage_CountObjects(page)); - EXPECT_TRUE(FPDFPage_GenerateContent(page)); - - FPDFBitmap_Destroy(bitmap); - FPDF_ClosePage(page); - FPDF_CloseDocument(doc); -} diff --git a/fpdfsdk/fpdfeditpage.cpp b/fpdfsdk/fpdfeditpage.cpp deleted file mode 100644 index 29cad9a752..0000000000 --- a/fpdfsdk/fpdfeditpage.cpp +++ /dev/null @@ -1,427 +0,0 @@ -// Copyright 2014 PDFium Authors. All rights reserved. -// 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_edit.h" - -#include -#include -#include - -#include "core/fpdfapi/edit/cpdf_pagecontentgenerator.h" -#include "core/fpdfapi/page/cpdf_form.h" -#include "core/fpdfapi/page/cpdf_formobject.h" -#include "core/fpdfapi/page/cpdf_imageobject.h" -#include "core/fpdfapi/page/cpdf_page.h" -#include "core/fpdfapi/page/cpdf_pageobject.h" -#include "core/fpdfapi/page/cpdf_pathobject.h" -#include "core/fpdfapi/page/cpdf_shadingobject.h" -#include "core/fpdfapi/parser/cpdf_array.h" -#include "core/fpdfapi/parser/cpdf_document.h" -#include "core/fpdfapi/parser/cpdf_number.h" -#include "core/fpdfapi/parser/cpdf_string.h" -#include "core/fpdfdoc/cpdf_annot.h" -#include "core/fpdfdoc/cpdf_annotlist.h" -#include "fpdfsdk/cpdfsdk_helpers.h" -#include "public/fpdf_formfill.h" -#include "third_party/base/logging.h" -#include "third_party/base/stl_util.h" - -#ifdef PDF_ENABLE_XFA -#include "fpdfsdk/fpdfxfa/cpdfxfa_context.h" -#include "fpdfsdk/fpdfxfa/cpdfxfa_page.h" -#endif // PDF_ENABLE_XFA - -#if _FX_OS_ == _FX_OS_ANDROID_ -#include -#else -#include -#endif - -namespace { - -static_assert(FPDF_PAGEOBJ_TEXT == CPDF_PageObject::TEXT, - "FPDF_PAGEOBJ_TEXT/CPDF_PageObject::TEXT mismatch"); -static_assert(FPDF_PAGEOBJ_PATH == CPDF_PageObject::PATH, - "FPDF_PAGEOBJ_PATH/CPDF_PageObject::PATH mismatch"); -static_assert(FPDF_PAGEOBJ_IMAGE == CPDF_PageObject::IMAGE, - "FPDF_PAGEOBJ_IMAGE/CPDF_PageObject::IMAGE mismatch"); -static_assert(FPDF_PAGEOBJ_SHADING == CPDF_PageObject::SHADING, - "FPDF_PAGEOBJ_SHADING/CPDF_PageObject::SHADING mismatch"); -static_assert(FPDF_PAGEOBJ_FORM == CPDF_PageObject::FORM, - "FPDF_PAGEOBJ_FORM/CPDF_PageObject::FORM mismatch"); - -const CPDF_ContentMarkItem* CPDFContentMarkItemFromFPDFPageObjectMark( - FPDF_PAGEOBJECTMARK mark) { - return static_cast(mark); -} - -bool IsPageObject(CPDF_Page* pPage) { - if (!pPage || !pPage->m_pFormDict || !pPage->m_pFormDict->KeyExist("Type")) - return false; - - CPDF_Object* pObject = pPage->m_pFormDict->GetObjectFor("Type")->GetDirect(); - return pObject && !pObject->GetString().Compare("Page"); -} - -void CalcBoundingBox(CPDF_PageObject* pPageObj) { - switch (pPageObj->GetType()) { - case CPDF_PageObject::TEXT: { - break; - } - case CPDF_PageObject::PATH: { - CPDF_PathObject* pPathObj = pPageObj->AsPath(); - pPathObj->CalcBoundingBox(); - break; - } - case CPDF_PageObject::IMAGE: { - CPDF_ImageObject* pImageObj = pPageObj->AsImage(); - pImageObj->CalcBoundingBox(); - break; - } - case CPDF_PageObject::SHADING: { - CPDF_ShadingObject* pShadingObj = pPageObj->AsShading(); - pShadingObj->CalcBoundingBox(); - break; - } - case CPDF_PageObject::FORM: { - CPDF_FormObject* pFormObj = pPageObj->AsForm(); - pFormObj->CalcBoundingBox(); - break; - } - default: { - NOTREACHED(); - break; - } - } -} - -} // namespace - -FPDF_EXPORT FPDF_DOCUMENT FPDF_CALLCONV FPDF_CreateNewDocument() { - auto pDoc = pdfium::MakeUnique(nullptr); - pDoc->CreateNewDoc(); - - time_t currentTime; - ByteString DateStr; - if (FSDK_IsSandBoxPolicyEnabled(FPDF_POLICY_MACHINETIME_ACCESS)) { - if (time(¤tTime) != -1) { - tm* pTM = localtime(¤tTime); - if (pTM) { - DateStr = ByteString::Format( - "D:%04d%02d%02d%02d%02d%02d", pTM->tm_year + 1900, pTM->tm_mon + 1, - pTM->tm_mday, pTM->tm_hour, pTM->tm_min, pTM->tm_sec); - } - } - } - - CPDF_Dictionary* pInfoDict = pDoc->GetInfo(); - if (pInfoDict) { - if (FSDK_IsSandBoxPolicyEnabled(FPDF_POLICY_MACHINETIME_ACCESS)) - pInfoDict->SetNewFor("CreationDate", DateStr, false); - pInfoDict->SetNewFor("Creator", L"PDFium"); - } - - // Caller takes ownership of pDoc. - return FPDFDocumentFromCPDFDocument(pDoc.release()); -} - -FPDF_EXPORT void FPDF_CALLCONV FPDFPage_Delete(FPDF_DOCUMENT document, - int page_index) { - if (UnderlyingDocumentType* pDoc = UnderlyingFromFPDFDocument(document)) - pDoc->DeletePage(page_index); -} - -FPDF_EXPORT FPDF_PAGE FPDF_CALLCONV FPDFPage_New(FPDF_DOCUMENT document, - int page_index, - double width, - double height) { - CPDF_Document* pDoc = CPDFDocumentFromFPDFDocument(document); - if (!pDoc) - return nullptr; - - page_index = pdfium::clamp(page_index, 0, pDoc->GetPageCount()); - CPDF_Dictionary* pPageDict = pDoc->CreateNewPage(page_index); - if (!pPageDict) - return nullptr; - - pPageDict->SetRectFor("MediaBox", CFX_FloatRect(0, 0, width, height)); - pPageDict->SetNewFor("Rotate", 0); - pPageDict->SetNewFor("Resources"); - -#ifdef PDF_ENABLE_XFA - auto pXFAPage = pdfium::MakeRetain( - static_cast(document), page_index); - pXFAPage->LoadPDFPage(pPageDict); - return pXFAPage.Leak(); // Caller takes ownership. -#else // PDF_ENABLE_XFA - auto pPage = pdfium::MakeUnique(pDoc, pPageDict, true); - pPage->ParseContent(); - return pPage.release(); // Caller takes ownership. -#endif // PDF_ENABLE_XFA -} - -FPDF_EXPORT int FPDF_CALLCONV FPDFPage_GetRotation(FPDF_PAGE page) { - CPDF_Page* pPage = CPDFPageFromFPDFPage(page); - return IsPageObject(pPage) ? pPage->GetPageRotation() : -1; -} - -FPDF_EXPORT void FPDF_CALLCONV FPDFPage_InsertObject(FPDF_PAGE page, - FPDF_PAGEOBJECT page_obj) { - CPDF_PageObject* pPageObj = CPDFPageObjectFromFPDFPageObject(page_obj); - if (!pPageObj) - return; - - std::unique_ptr pPageObjHolder(pPageObj); - CPDF_Page* pPage = CPDFPageFromFPDFPage(page); - if (!IsPageObject(pPage)) - return; - pPageObj->SetDirty(true); - - pPage->AppendPageObject(std::move(pPageObjHolder)); - CalcBoundingBox(pPageObj); -} - -FPDF_EXPORT FPDF_BOOL FPDF_CALLCONV -FPDFPage_RemoveObject(FPDF_PAGE page, FPDF_PAGEOBJECT page_obj) { - CPDF_PageObject* pPageObj = CPDFPageObjectFromFPDFPageObject(page_obj); - if (!pPageObj) - return false; - - CPDF_Page* pPage = CPDFPageFromFPDFPage(page); - if (!IsPageObject(pPage)) - return false; - - return pPage->RemovePageObject(pPageObj); -} - -FPDF_EXPORT int FPDF_CALLCONV FPDFPage_CountObject(FPDF_PAGE page) { - return FPDFPage_CountObjects(page); -} - -FPDF_EXPORT int FPDF_CALLCONV FPDFPage_CountObjects(FPDF_PAGE page) { - CPDF_Page* pPage = CPDFPageFromFPDFPage(page); - if (!IsPageObject(pPage)) - return -1; - - return pPage->GetPageObjectCount(); -} - -FPDF_EXPORT FPDF_PAGEOBJECT FPDF_CALLCONV FPDFPage_GetObject(FPDF_PAGE page, - int index) { - CPDF_Page* pPage = CPDFPageFromFPDFPage(page); - if (!IsPageObject(pPage)) - return nullptr; - - return pPage->GetPageObjectByIndex(index); -} - -FPDF_EXPORT FPDF_BOOL FPDF_CALLCONV FPDFPage_HasTransparency(FPDF_PAGE page) { - CPDF_Page* pPage = CPDFPageFromFPDFPage(page); - return pPage && pPage->BackgroundAlphaNeeded(); -} - -FPDF_EXPORT void FPDF_CALLCONV FPDFPageObj_Destroy(FPDF_PAGEOBJECT page_obj) { - delete CPDFPageObjectFromFPDFPageObject(page_obj); -} - -FPDF_EXPORT int FPDF_CALLCONV -FPDFPageObj_CountMarks(FPDF_PAGEOBJECT page_object) { - if (!page_object) - return -1; - - const auto& mark = - CPDFPageObjectFromFPDFPageObject(page_object)->m_ContentMark; - return mark.HasRef() ? mark.CountItems() : 0; -} - -FPDF_EXPORT FPDF_PAGEOBJECTMARK FPDF_CALLCONV -FPDFPageObj_GetMark(FPDF_PAGEOBJECT page_object, unsigned long index) { - if (!page_object) - return nullptr; - - const auto& mark = - CPDFPageObjectFromFPDFPageObject(page_object)->m_ContentMark; - if (!mark.HasRef()) - return nullptr; - - if (index >= mark.CountItems()) - return nullptr; - - return static_cast(&mark.GetItem(index)); -} - -FPDF_EXPORT unsigned long FPDF_CALLCONV -FPDFPageObjMark_GetName(FPDF_PAGEOBJECTMARK mark, - void* buffer, - unsigned long buflen) { - if (!mark) - return 0; - - const CPDF_ContentMarkItem* pMarkItem = - CPDFContentMarkItemFromFPDFPageObjectMark(mark); - - return Utf16EncodeMaybeCopyAndReturnLength( - WideString::FromUTF8(pMarkItem->GetName().AsStringView()), buffer, - buflen); -} - -FPDF_EXPORT FPDF_BOOL FPDF_CALLCONV -FPDFPageObj_HasTransparency(FPDF_PAGEOBJECT pageObject) { - if (!pageObject) - return false; - - CPDF_PageObject* pPageObj = CPDFPageObjectFromFPDFPageObject(pageObject); - int blend_type = pPageObj->m_GeneralState.GetBlendType(); - if (blend_type != FXDIB_BLEND_NORMAL) - return true; - - CPDF_Dictionary* pSMaskDict = - ToDictionary(pPageObj->m_GeneralState.GetSoftMask()); - if (pSMaskDict) - return true; - - if (pPageObj->m_GeneralState.GetFillAlpha() != 1.0f) - return true; - - if (pPageObj->IsPath() && pPageObj->m_GeneralState.GetStrokeAlpha() != 1.0f) { - return true; - } - - if (pPageObj->IsForm()) { - const CPDF_Form* pForm = pPageObj->AsForm()->form(); - if (pForm) { - int trans = pForm->m_iTransparency; - if ((trans & PDFTRANS_ISOLATED) || (trans & PDFTRANS_GROUP)) - return true; - } - } - - return false; -} - -FPDF_EXPORT int FPDF_CALLCONV FPDFPageObj_GetType(FPDF_PAGEOBJECT pageObject) { - if (!pageObject) - return FPDF_PAGEOBJ_UNKNOWN; - - CPDF_PageObject* pPageObj = CPDFPageObjectFromFPDFPageObject(pageObject); - return pPageObj->GetType(); -} - -FPDF_EXPORT FPDF_BOOL FPDF_CALLCONV FPDFPage_GenerateContent(FPDF_PAGE page) { - CPDF_Page* pPage = CPDFPageFromFPDFPage(page); - if (!IsPageObject(pPage)) - return false; - - CPDF_PageContentGenerator CG(pPage); - CG.GenerateContent(); - return true; -} - -FPDF_EXPORT void FPDF_CALLCONV -FPDFPageObj_Transform(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); - pPageObj->Transform(matrix); -} - -FPDF_EXPORT void FPDF_CALLCONV -FPDFPageObj_SetBlendMode(FPDF_PAGEOBJECT page_object, - FPDF_BYTESTRING blend_mode) { - CPDF_PageObject* pPageObj = CPDFPageObjectFromFPDFPageObject(page_object); - if (!pPageObj) - return; - - pPageObj->m_GeneralState.SetBlendMode(blend_mode); - pPageObj->SetDirty(true); -} - -FPDF_EXPORT void FPDF_CALLCONV FPDFPage_TransformAnnots(FPDF_PAGE page, - double a, - double b, - double c, - double d, - double e, - double f) { - CPDF_Page* pPage = CPDFPageFromFPDFPage(page); - if (!pPage) - return; - - CPDF_AnnotList AnnotList(pPage); - for (size_t i = 0; i < AnnotList.Count(); ++i) { - CPDF_Annot* pAnnot = AnnotList.GetAt(i); - CFX_Matrix matrix((float)a, (float)b, (float)c, (float)d, (float)e, - (float)f); - CFX_FloatRect rect = matrix.TransformRect(pAnnot->GetRect()); - - CPDF_Dictionary* pAnnotDict = pAnnot->GetAnnotDict(); - CPDF_Array* pRectArray = pAnnotDict->GetArrayFor("Rect"); - if (pRectArray) - pRectArray->Clear(); - else - pRectArray = pAnnotDict->SetNewFor("Rect"); - - pRectArray->AddNew(rect.left); - pRectArray->AddNew(rect.bottom); - pRectArray->AddNew(rect.right); - pRectArray->AddNew(rect.top); - - // TODO(unknown): Transform AP's rectangle - } -} - -FPDF_EXPORT void FPDF_CALLCONV FPDFPage_SetRotation(FPDF_PAGE page, - int rotate) { - CPDF_Page* pPage = CPDFPageFromFPDFPage(page); - if (!IsPageObject(pPage)) - return; - - rotate %= 4; - pPage->m_pFormDict->SetNewFor("Rotate", rotate * 90); -} - -FPDF_BOOL FPDFPageObj_SetFillColor(FPDF_PAGEOBJECT page_object, - unsigned int R, - unsigned int G, - unsigned int B, - unsigned int A) { - if (!page_object || R > 255 || G > 255 || B > 255 || A > 255) - return false; - - float rgb[3] = {R / 255.f, G / 255.f, B / 255.f}; - auto* pPageObj = CPDFPageObjectFromFPDFPageObject(page_object); - pPageObj->m_GeneralState.SetFillAlpha(A / 255.f); - pPageObj->m_ColorState.SetFillColor( - CPDF_ColorSpace::GetStockCS(PDFCS_DEVICERGB), rgb, 3); - pPageObj->SetDirty(true); - return true; -} - -FPDF_EXPORT FPDF_BOOL FPDF_CALLCONV -FPDFPageObj_GetBounds(FPDF_PAGEOBJECT pageObject, - float* left, - float* bottom, - float* right, - float* top) { - if (!pageObject) - return false; - - CPDF_PageObject* pPageObj = CPDFPageObjectFromFPDFPageObject(pageObject); - CFX_FloatRect bbox = pPageObj->GetRect(); - *left = bbox.left; - *bottom = bbox.bottom; - *right = bbox.right; - *top = bbox.top; - return true; -} diff --git a/fpdfsdk/fpdfeditpath.cpp b/fpdfsdk/fpdfeditpath.cpp deleted file mode 100644 index 745c3b763b..0000000000 --- a/fpdfsdk/fpdfeditpath.cpp +++ /dev/null @@ -1,292 +0,0 @@ -// Copyright 2017 PDFium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#include - -#include "public/fpdf_edit.h" - -#include "core/fpdfapi/page/cpdf_path.h" -#include "core/fpdfapi/page/cpdf_pathobject.h" -#include "core/fxcrt/fx_system.h" -#include "fpdfsdk/cpdfsdk_helpers.h" -#include "third_party/base/ptr_util.h" - -// These checks are here because core/ and public/ cannot depend on each other. -static_assert(CFX_GraphStateData::LineCapButt == FPDF_LINECAP_BUTT, - "CFX_GraphStateData::LineCapButt value mismatch"); -static_assert(CFX_GraphStateData::LineCapRound == FPDF_LINECAP_ROUND, - "CFX_GraphStateData::LineCapRound value mismatch"); -static_assert(CFX_GraphStateData::LineCapSquare == - FPDF_LINECAP_PROJECTING_SQUARE, - "CFX_GraphStateData::LineCapSquare value mismatch"); - -static_assert(CFX_GraphStateData::LineJoinMiter == FPDF_LINEJOIN_MITER, - "CFX_GraphStateData::LineJoinMiter value mismatch"); -static_assert(CFX_GraphStateData::LineJoinRound == FPDF_LINEJOIN_ROUND, - "CFX_GraphStateData::LineJoinRound value mismatch"); -static_assert(CFX_GraphStateData::LineJoinBevel == FPDF_LINEJOIN_BEVEL, - "CFX_GraphStateData::LineJoinBevel value mismatch"); - -static_assert(static_cast(FXPT_TYPE::LineTo) == FPDF_SEGMENT_LINETO, - "FXPT_TYPE::LineTo value mismatch"); -static_assert(static_cast(FXPT_TYPE::BezierTo) == FPDF_SEGMENT_BEZIERTO, - "FXPT_TYPE::BezierTo value mismatch"); -static_assert(static_cast(FXPT_TYPE::MoveTo) == FPDF_SEGMENT_MOVETO, - "FXPT_TYPE::MoveTo value mismatch"); - -namespace { - -CPDF_PathObject* CPDFPathObjectFromFPDFPageObject(FPDF_PAGEOBJECT page_object) { - auto* obj = CPDFPageObjectFromFPDFPageObject(page_object); - return obj ? obj->AsPath() : nullptr; -} - -const FX_PATHPOINT* FXPathPointFromFPDFPathSegment(FPDF_PATHSEGMENT segment) { - return static_cast(segment); -} - -unsigned int GetAlphaAsUnsignedInt(float alpha) { - return static_cast(alpha * 255.f + 0.5f); -} - -} // namespace - -FPDF_EXPORT FPDF_PAGEOBJECT FPDF_CALLCONV FPDFPageObj_CreateNewPath(float x, - float y) { - auto pPathObj = pdfium::MakeUnique(); - pPathObj->m_Path.AppendPoint(CFX_PointF(x, y), FXPT_TYPE::MoveTo, false); - pPathObj->DefaultStates(); - return pPathObj.release(); // Caller takes ownership. -} - -FPDF_EXPORT FPDF_PAGEOBJECT FPDF_CALLCONV FPDFPageObj_CreateNewRect(float x, - float y, - float w, - float h) { - auto pPathObj = pdfium::MakeUnique(); - pPathObj->m_Path.AppendRect(x, y, x + w, y + h); - pPathObj->DefaultStates(); - return pPathObj.release(); // Caller takes ownership. -} - -FPDF_EXPORT FPDF_BOOL FPDF_CALLCONV -FPDFPath_SetStrokeColor(FPDF_PAGEOBJECT path, - unsigned int R, - unsigned int G, - unsigned int B, - unsigned int A) { - auto* pPathObj = CPDFPathObjectFromFPDFPageObject(path); - if (!pPathObj || R > 255 || G > 255 || B > 255 || A > 255) - return false; - - float rgb[3] = {R / 255.f, G / 255.f, B / 255.f}; - pPathObj->m_GeneralState.SetStrokeAlpha(A / 255.f); - pPathObj->m_ColorState.SetStrokeColor( - CPDF_ColorSpace::GetStockCS(PDFCS_DEVICERGB), rgb, 3); - pPathObj->SetDirty(true); - return true; -} - -FPDF_EXPORT FPDF_BOOL FPDF_CALLCONV -FPDFPath_GetStrokeColor(FPDF_PAGEOBJECT path, - unsigned int* R, - unsigned int* G, - unsigned int* B, - unsigned int* A) { - auto* pPathObj = CPDFPathObjectFromFPDFPageObject(path); - if (!pPathObj || !R || !G || !B || !A) - return false; - - FX_COLORREF strokeColor = pPathObj->m_ColorState.GetStrokeColorRef(); - *R = FXSYS_GetRValue(strokeColor); - *G = FXSYS_GetGValue(strokeColor); - *B = FXSYS_GetBValue(strokeColor); - *A = GetAlphaAsUnsignedInt(pPathObj->m_GeneralState.GetStrokeAlpha()); - return true; -} - -FPDF_EXPORT FPDF_BOOL FPDF_CALLCONV -FPDFPath_SetStrokeWidth(FPDF_PAGEOBJECT path, float width) { - auto* pPathObj = CPDFPathObjectFromFPDFPageObject(path); - if (!pPathObj || width < 0.0f) - return false; - - pPathObj->m_GraphState.SetLineWidth(width); - pPathObj->SetDirty(true); - return true; -} - -FPDF_EXPORT FPDF_BOOL FPDF_CALLCONV FPDFPath_SetFillColor(FPDF_PAGEOBJECT path, - unsigned int R, - unsigned int G, - unsigned int B, - unsigned int A) { - return FPDFPageObj_SetFillColor(path, R, G, B, A); -} - -FPDF_EXPORT FPDF_BOOL FPDF_CALLCONV FPDFPath_GetFillColor(FPDF_PAGEOBJECT path, - unsigned int* R, - unsigned int* G, - unsigned int* B, - unsigned int* A) { - auto* pPathObj = CPDFPathObjectFromFPDFPageObject(path); - if (!pPathObj || !R || !G || !B || !A) - return false; - - FX_COLORREF fillColor = pPathObj->m_ColorState.GetFillColorRef(); - *R = FXSYS_GetRValue(fillColor); - *G = FXSYS_GetGValue(fillColor); - *B = FXSYS_GetBValue(fillColor); - *A = GetAlphaAsUnsignedInt(pPathObj->m_GeneralState.GetFillAlpha()); - return true; -} - -FPDF_EXPORT int FPDF_CALLCONV FPDFPath_CountSegments(FPDF_PAGEOBJECT path) { - auto* pPathObj = CPDFPathObjectFromFPDFPageObject(path); - if (!pPathObj) - return -1; - return pdfium::CollectionSize(pPathObj->m_Path.GetPoints()); -} - -FPDF_EXPORT FPDF_PATHSEGMENT FPDF_CALLCONV -FPDFPath_GetPathSegment(FPDF_PAGEOBJECT path, int index) { - auto* pPathObj = CPDFPathObjectFromFPDFPageObject(path); - if (!pPathObj) - return nullptr; - - const std::vector& points = pPathObj->m_Path.GetPoints(); - return pdfium::IndexInBounds(points, index) ? &points[index] : nullptr; -} - -FPDF_EXPORT FPDF_BOOL FPDF_CALLCONV FPDFPath_MoveTo(FPDF_PAGEOBJECT path, - float x, - float y) { - auto* pPathObj = CPDFPathObjectFromFPDFPageObject(path); - if (!pPathObj) - return false; - - pPathObj->m_Path.AppendPoint(CFX_PointF(x, y), FXPT_TYPE::MoveTo, false); - pPathObj->SetDirty(true); - return true; -} - -FPDF_EXPORT FPDF_BOOL FPDF_CALLCONV FPDFPath_LineTo(FPDF_PAGEOBJECT path, - float x, - float y) { - auto* pPathObj = CPDFPathObjectFromFPDFPageObject(path); - if (!pPathObj) - return false; - - pPathObj->m_Path.AppendPoint(CFX_PointF(x, y), FXPT_TYPE::LineTo, false); - pPathObj->SetDirty(true); - return true; -} - -FPDF_EXPORT FPDF_BOOL FPDF_CALLCONV FPDFPath_BezierTo(FPDF_PAGEOBJECT path, - float x1, - float y1, - float x2, - float y2, - float x3, - float y3) { - auto* pPathObj = CPDFPathObjectFromFPDFPageObject(path); - if (!pPathObj) - return false; - - pPathObj->m_Path.AppendPoint(CFX_PointF(x1, y1), FXPT_TYPE::BezierTo, false); - pPathObj->m_Path.AppendPoint(CFX_PointF(x2, y2), FXPT_TYPE::BezierTo, false); - pPathObj->m_Path.AppendPoint(CFX_PointF(x3, y3), FXPT_TYPE::BezierTo, false); - pPathObj->SetDirty(true); - return true; -} - -FPDF_EXPORT FPDF_BOOL FPDF_CALLCONV FPDFPath_Close(FPDF_PAGEOBJECT path) { - auto* pPathObj = CPDFPathObjectFromFPDFPageObject(path); - if (!pPathObj) - return false; - - if (pPathObj->m_Path.GetPoints().empty()) - return false; - - pPathObj->m_Path.ClosePath(); - pPathObj->SetDirty(true); - return true; -} - -FPDF_EXPORT FPDF_BOOL FPDF_CALLCONV FPDFPath_SetDrawMode(FPDF_PAGEOBJECT path, - int fillmode, - FPDF_BOOL stroke) { - auto* pPathObj = CPDFPathObjectFromFPDFPageObject(path); - if (!pPathObj) - return false; - - if (fillmode == FPDF_FILLMODE_ALTERNATE) - pPathObj->m_FillType = FXFILL_ALTERNATE; - else if (fillmode == FPDF_FILLMODE_WINDING) - pPathObj->m_FillType = FXFILL_WINDING; - else - pPathObj->m_FillType = 0; - pPathObj->m_bStroke = stroke != 0; - pPathObj->SetDirty(true); - return true; -} - -FPDF_EXPORT void FPDF_CALLCONV FPDFPath_SetLineJoin(FPDF_PAGEOBJECT path, - int line_join) { - if (!path) - return; - if (line_join < - static_cast(CFX_GraphStateData::LineJoin::LineJoinMiter) || - line_join > - static_cast(CFX_GraphStateData::LineJoin::LineJoinBevel)) { - return; - } - auto* pPathObj = CPDFPageObjectFromFPDFPageObject(path); - CFX_GraphStateData::LineJoin lineJoin = - static_cast(line_join); - pPathObj->m_GraphState.SetLineJoin(lineJoin); - pPathObj->SetDirty(true); -} - -FPDF_EXPORT void FPDF_CALLCONV FPDFPath_SetLineCap(FPDF_PAGEOBJECT path, - int line_cap) { - if (!path) - return; - if (line_cap < static_cast(CFX_GraphStateData::LineCap::LineCapButt) || - line_cap > static_cast(CFX_GraphStateData::LineCap::LineCapSquare)) { - return; - } - auto* pPathObj = CPDFPageObjectFromFPDFPageObject(path); - CFX_GraphStateData::LineCap lineCap = - static_cast(line_cap); - pPathObj->m_GraphState.SetLineCap(lineCap); - pPathObj->SetDirty(true); -} - -FPDF_EXPORT FPDF_BOOL FPDF_CALLCONV -FPDFPathSegment_GetPoint(FPDF_PATHSEGMENT segment, float* x, float* y) { - auto* pPathPoint = FXPathPointFromFPDFPathSegment(segment); - if (!pPathPoint || !x || !y) - return false; - - *x = pPathPoint->m_Point.x; - *y = pPathPoint->m_Point.y; - - return true; -} - -FPDF_EXPORT int FPDF_CALLCONV -FPDFPathSegment_GetType(FPDF_PATHSEGMENT segment) { - auto* pPathPoint = FXPathPointFromFPDFPathSegment(segment); - - return pPathPoint ? static_cast(pPathPoint->m_Type) - : FPDF_SEGMENT_UNKNOWN; -} - -FPDF_EXPORT FPDF_BOOL FPDF_CALLCONV -FPDFPathSegment_GetClose(FPDF_PATHSEGMENT segment) { - auto* pPathPoint = FXPathPointFromFPDFPathSegment(segment); - - return pPathPoint ? pPathPoint->m_CloseFigure : false; -} diff --git a/fpdfsdk/fpdfeditpath_embeddertest.cpp b/fpdfsdk/fpdfeditpath_embeddertest.cpp deleted file mode 100644 index 59e5dbbabf..0000000000 --- a/fpdfsdk/fpdfeditpath_embeddertest.cpp +++ /dev/null @@ -1,63 +0,0 @@ -// Copyright 2017 PDFium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#include "core/fxcrt/fx_system.h" -#include "public/fpdf_edit.h" -#include "testing/embedder_test.h" -#include "testing/gtest/include/gtest/gtest.h" -#include "testing/test_support.h" - -class FPDFEditPathEmbedderTest : public EmbedderTest {}; - -TEST_F(FPDFEditPathEmbedderTest, VerifyCorrectColoursReturned) { - CreateEmptyDocument(); - FPDF_PAGE page = FPDFPage_New(document(), 0, 612, 792); - - for (size_t i = 0; i < 256; ++i) { - FPDF_PAGEOBJECT path = FPDFPageObj_CreateNewPath(400, 100); - EXPECT_TRUE(FPDFPath_SetFillColor(path, i, i, i, i)); - EXPECT_TRUE(FPDFPath_SetStrokeColor(path, i, i, i, i)); - EXPECT_TRUE(FPDFPath_SetDrawMode(path, FPDF_FILLMODE_ALTERNATE, 0)); - EXPECT_TRUE(FPDFPath_LineTo(path, 400, 200)); - EXPECT_TRUE(FPDFPath_LineTo(path, 300, 100)); - EXPECT_TRUE(FPDFPath_Close(path)); - - FPDFPage_InsertObject(page, path); - } - - EXPECT_TRUE(FPDFPage_GenerateContent(page)); - EXPECT_TRUE(FPDF_SaveAsCopy(document(), this, 0)); - FPDF_ClosePage(page); - page = nullptr; - - OpenSavedDocument(); - page = LoadSavedPage(0); - ASSERT(page); - - for (size_t i = 0; i < 256; ++i) { - FPDF_PAGEOBJECT path = FPDFPage_GetObject(page, i); - ASSERT(path); - - EXPECT_EQ(FPDF_PAGEOBJ_PATH, FPDFPageObj_GetType(path)); - - unsigned int r; - unsigned int g; - unsigned int b; - unsigned int a; - FPDFPath_GetFillColor(path, &r, &g, &b, &a); - EXPECT_EQ(i, r); - EXPECT_EQ(i, g); - EXPECT_EQ(i, b); - EXPECT_EQ(i, a); - - FPDFPath_GetStrokeColor(path, &r, &g, &b, &a); - EXPECT_EQ(i, r); - EXPECT_EQ(i, g); - EXPECT_EQ(i, b); - EXPECT_EQ(i, a); - } - - CloseSavedPage(page); - CloseSavedDocument(); -} diff --git a/fpdfsdk/fpdfedittext.cpp b/fpdfsdk/fpdfedittext.cpp deleted file mode 100644 index 8155003a1f..0000000000 --- a/fpdfsdk/fpdfedittext.cpp +++ /dev/null @@ -1,499 +0,0 @@ -// Copyright 2017 PDFium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#include -#include -#include -#include -#include -#include - -#include "core/fpdfapi/cpdf_modulemgr.h" -#include "core/fpdfapi/font/cpdf_font.h" -#include "core/fpdfapi/font/cpdf_type1font.h" -#include "core/fpdfapi/page/cpdf_docpagedata.h" -#include "core/fpdfapi/page/cpdf_textobject.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_name.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_extension.h" -#include "core/fxge/cfx_fontmgr.h" -#include "core/fxge/fx_font.h" -#include "fpdfsdk/cpdfsdk_helpers.h" -#include "public/fpdf_edit.h" - -namespace { - -CPDF_Dictionary* LoadFontDesc(CPDF_Document* pDoc, - const ByteString& font_name, - CFX_Font* pFont, - const uint8_t* data, - uint32_t size, - int font_type) { - CPDF_Dictionary* pFontDesc = pDoc->NewIndirect(); - pFontDesc->SetNewFor("Type", "FontDescriptor"); - pFontDesc->SetNewFor("FontName", font_name); - int flags = 0; - if (FXFT_Is_Face_fixedwidth(pFont->GetFace())) - flags |= FXFONT_FIXED_PITCH; - if (font_name.Contains("Serif")) - flags |= FXFONT_SERIF; - if (FXFT_Is_Face_Italic(pFont->GetFace())) - flags |= FXFONT_ITALIC; - if (FXFT_Is_Face_Bold(pFont->GetFace())) - flags |= FXFONT_BOLD; - - // TODO(npm): How do I know if a font is symbolic, script, allcap, smallcap - flags |= FXFONT_NONSYMBOLIC; - - pFontDesc->SetNewFor("Flags", flags); - FX_RECT bbox; - pFont->GetBBox(bbox); - pFontDesc->SetRectFor("FontBBox", CFX_FloatRect(bbox)); - - // TODO(npm): calculate italic angle correctly - pFontDesc->SetNewFor("ItalicAngle", pFont->IsItalic() ? -12 : 0); - - pFontDesc->SetNewFor("Ascent", pFont->GetAscent()); - pFontDesc->SetNewFor("Descent", pFont->GetDescent()); - - // TODO(npm): calculate the capheight, stemV correctly - pFontDesc->SetNewFor("CapHeight", pFont->GetAscent()); - pFontDesc->SetNewFor("StemV", pFont->IsBold() ? 120 : 70); - - CPDF_Stream* pStream = pDoc->NewIndirect(); - pStream->SetData(data, size); - // TODO(npm): Lengths for Type1 fonts. - if (font_type == FPDF_FONT_TRUETYPE) { - pStream->GetDict()->SetNewFor("Length1", - static_cast(size)); - } - ByteString fontFile = font_type == FPDF_FONT_TYPE1 ? "FontFile" : "FontFile2"; - pFontDesc->SetNewFor(fontFile, pDoc, pStream->GetObjNum()); - return pFontDesc; -} - -const char ToUnicodeStart[] = - "/CIDInit /ProcSet findresource begin\n" - "12 dict begin\n" - "begincmap\n" - "/CIDSystemInfo\n" - "<> def\n" - "/CMapName /Adobe-Identity-H def\n" - "CMapType 2 def\n" - "1 begincodespacerange\n" - "<0000> \n" - "endcodespacerange\n"; - -const char ToUnicodeEnd[] = - "endcmap\n" - "CMapName currentdict /CMap defineresource pop\n" - "end\n" - "end\n"; - -void AddCharcode(std::ostringstream* pBuffer, uint32_t number) { - ASSERT(number <= 0xFFFF); - *pBuffer << "<"; - char ans[4]; - FXSYS_IntToFourHexChars(number, ans); - for (size_t i = 0; i < 4; ++i) - *pBuffer << ans[i]; - *pBuffer << ">"; -} - -// PDF spec 1.7 Section 5.9.2: "Unicode character sequences as expressed in -// UTF-16BE encoding." See https://en.wikipedia.org/wiki/UTF-16#Description -void AddUnicode(std::ostringstream* pBuffer, uint32_t unicode) { - if (unicode >= 0xD800 && unicode <= 0xDFFF) - unicode = 0; - - char ans[8]; - *pBuffer << "<"; - size_t numChars = FXSYS_ToUTF16BE(unicode, ans); - for (size_t i = 0; i < numChars; ++i) - *pBuffer << ans[i]; - *pBuffer << ">"; -} - -// Loads the charcode to unicode mapping into a stream -CPDF_Stream* LoadUnicode(CPDF_Document* pDoc, - const std::map& to_unicode) { - // A map charcode->unicode - std::map char_to_uni; - // A map to vector v of unicode characters of size (end - // - start + 1). This abbreviates: start->v[0], start+1->v[1], etc. PDF spec - // 1.7 Section 5.9.2 says that only the last byte of the unicode may change. - std::map, std::vector> - map_range_vector; - // A map -> unicode - // This abbreviates: start->unicode, start+1->unicode+1, etc. - // PDF spec 1.7 Section 5.9.2 says that only the last byte of the unicode may - // change. - std::map, uint32_t> map_range; - - // Calculate the maps - for (auto iter = to_unicode.begin(); iter != to_unicode.end(); ++iter) { - uint32_t firstCharcode = iter->first; - uint32_t firstUnicode = iter->second; - if (std::next(iter) == to_unicode.end() || - firstCharcode + 1 != std::next(iter)->first) { - char_to_uni[firstCharcode] = firstUnicode; - continue; - } - ++iter; - uint32_t curCharcode = iter->first; - uint32_t curUnicode = iter->second; - if (curCharcode % 256 == 0) { - char_to_uni[firstCharcode] = firstUnicode; - char_to_uni[curCharcode] = curUnicode; - continue; - } - const size_t maxExtra = 255 - (curCharcode % 256); - auto next_it = std::next(iter); - if (firstUnicode + 1 != curUnicode) { - // Consecutive charcodes mapping to non-consecutive unicodes - std::vector unicodes; - unicodes.push_back(firstUnicode); - unicodes.push_back(curUnicode); - for (size_t i = 0; i < maxExtra; ++i) { - if (next_it == to_unicode.end() || curCharcode + 1 != next_it->first) - break; - ++iter; - ++curCharcode; - unicodes.push_back(iter->second); - next_it = std::next(iter); - } - ASSERT(iter->first - firstCharcode + 1 == unicodes.size()); - map_range_vector[std::make_pair(firstCharcode, iter->first)] = unicodes; - continue; - } - // Consecutive charcodes mapping to consecutive unicodes - for (size_t i = 0; i < maxExtra; ++i) { - if (next_it == to_unicode.end() || curCharcode + 1 != next_it->first || - curUnicode + 1 != next_it->second) { - break; - } - ++iter; - ++curCharcode; - ++curUnicode; - next_it = std::next(iter); - } - map_range[std::make_pair(firstCharcode, curCharcode)] = firstUnicode; - } - std::ostringstream buffer; - buffer << ToUnicodeStart; - // Add maps to buffer - buffer << static_cast(char_to_uni.size()) << " beginbfchar\n"; - for (const auto& iter : char_to_uni) { - AddCharcode(&buffer, iter.first); - buffer << " "; - AddUnicode(&buffer, iter.second); - buffer << "\n"; - } - buffer << "endbfchar\n" - << static_cast(map_range_vector.size() + map_range.size()) - << " beginbfrange\n"; - for (const auto& iter : map_range_vector) { - const std::pair& charcodeRange = iter.first; - AddCharcode(&buffer, charcodeRange.first); - buffer << " "; - AddCharcode(&buffer, charcodeRange.second); - buffer << " ["; - const std::vector& unicodes = iter.second; - for (size_t i = 0; i < unicodes.size(); ++i) { - uint32_t uni = unicodes[i]; - AddUnicode(&buffer, uni); - if (i != unicodes.size() - 1) - buffer << " "; - } - buffer << "]\n"; - } - for (const auto& iter : map_range) { - const std::pair& charcodeRange = iter.first; - AddCharcode(&buffer, charcodeRange.first); - buffer << " "; - AddCharcode(&buffer, charcodeRange.second); - buffer << " "; - AddUnicode(&buffer, iter.second); - buffer << "\n"; - } - buffer << "endbfrange\n"; - buffer << ToUnicodeEnd; - // TODO(npm): Encrypt / Compress? - CPDF_Stream* stream = pDoc->NewIndirect(); - stream->SetData(&buffer); - return stream; -} - -const uint32_t kMaxSimpleFontChar = 0xFF; - -void* LoadSimpleFont(CPDF_Document* pDoc, - std::unique_ptr pFont, - const uint8_t* data, - uint32_t size, - int font_type) { - CPDF_Dictionary* fontDict = pDoc->NewIndirect(); - fontDict->SetNewFor("Type", "Font"); - fontDict->SetNewFor( - "Subtype", font_type == FPDF_FONT_TYPE1 ? "Type1" : "TrueType"); - ByteString name = pFont->GetFaceName(); - if (name.IsEmpty()) - name = "Unnamed"; - fontDict->SetNewFor("BaseFont", name); - - uint32_t glyphIndex; - uint32_t currentChar = FXFT_Get_First_Char(pFont->GetFace(), &glyphIndex); - if (currentChar > kMaxSimpleFontChar || glyphIndex == 0) - return nullptr; - fontDict->SetNewFor("FirstChar", static_cast(currentChar)); - CPDF_Array* widthsArray = pDoc->NewIndirect(); - while (true) { - uint32_t width = - std::min(pFont->GetGlyphWidth(glyphIndex), - static_cast(std::numeric_limits::max())); - widthsArray->AddNew(static_cast(width)); - uint32_t nextChar = - FXFT_Get_Next_Char(pFont->GetFace(), currentChar, &glyphIndex); - // Simple fonts have 1-byte charcodes only. - if (nextChar > kMaxSimpleFontChar || glyphIndex == 0) - break; - for (uint32_t i = currentChar + 1; i < nextChar; i++) - widthsArray->AddNew(0); - currentChar = nextChar; - } - fontDict->SetNewFor("LastChar", static_cast(currentChar)); - fontDict->SetNewFor("Widths", pDoc, widthsArray->GetObjNum()); - CPDF_Dictionary* pFontDesc = - LoadFontDesc(pDoc, name, pFont.get(), data, size, font_type); - - fontDict->SetNewFor("FontDescriptor", pDoc, - pFontDesc->GetObjNum()); - return pDoc->LoadFont(fontDict); -} - -const uint32_t kMaxUnicode = 0x10FFFF; - -void* LoadCompositeFont(CPDF_Document* pDoc, - std::unique_ptr pFont, - const uint8_t* data, - uint32_t size, - int font_type) { - CPDF_Dictionary* fontDict = pDoc->NewIndirect(); - fontDict->SetNewFor("Type", "Font"); - fontDict->SetNewFor("Subtype", "Type0"); - // TODO(npm): Get the correct encoding, if it's not identity. - ByteString encoding = "Identity-H"; - fontDict->SetNewFor("Encoding", encoding); - ByteString name = pFont->GetFaceName(); - if (name.IsEmpty()) - name = "Unnamed"; - fontDict->SetNewFor( - "BaseFont", font_type == FPDF_FONT_TYPE1 ? name + "-" + encoding : name); - - CPDF_Dictionary* pCIDFont = pDoc->NewIndirect(); - pCIDFont->SetNewFor("Type", "Font"); - pCIDFont->SetNewFor("Subtype", font_type == FPDF_FONT_TYPE1 - ? "CIDFontType0" - : "CIDFontType2"); - pCIDFont->SetNewFor("BaseFont", name); - - // TODO(npm): Maybe use FT_Get_CID_Registry_Ordering_Supplement to get the - // CIDSystemInfo - CPDF_Dictionary* pCIDSystemInfo = pDoc->NewIndirect(); - pCIDSystemInfo->SetNewFor("Registry", "Adobe"); - pCIDSystemInfo->SetNewFor("Ordering", "Identity"); - pCIDSystemInfo->SetNewFor("Supplement", 0); - pCIDFont->SetNewFor("CIDSystemInfo", pDoc, - pCIDSystemInfo->GetObjNum()); - - CPDF_Dictionary* pFontDesc = - LoadFontDesc(pDoc, name, pFont.get(), data, size, font_type); - pCIDFont->SetNewFor("FontDescriptor", pDoc, - pFontDesc->GetObjNum()); - - uint32_t glyphIndex; - uint32_t currentChar = FXFT_Get_First_Char(pFont->GetFace(), &glyphIndex); - // If it doesn't have a single char, just fail - if (glyphIndex == 0 || currentChar > kMaxUnicode) - return nullptr; - - std::map to_unicode; - std::map widths; - while (true) { - if (currentChar > kMaxUnicode) - break; - - widths[glyphIndex] = pFont->GetGlyphWidth(glyphIndex); - to_unicode[glyphIndex] = currentChar; - currentChar = - FXFT_Get_Next_Char(pFont->GetFace(), currentChar, &glyphIndex); - if (glyphIndex == 0) - break; - } - CPDF_Array* widthsArray = pDoc->NewIndirect(); - for (auto it = widths.begin(); it != widths.end(); ++it) { - int ch = it->first; - int w = it->second; - if (std::next(it) == widths.end()) { - // Only one char left, use format c [w] - auto oneW = pdfium::MakeUnique(); - oneW->AddNew(w); - widthsArray->AddNew(ch); - widthsArray->Add(std::move(oneW)); - break; - } - ++it; - int next_ch = it->first; - int next_w = it->second; - if (next_ch == ch + 1 && next_w == w) { - // The array can have a group c_first c_last w: all CIDs in the range from - // c_first to c_last will have width w - widthsArray->AddNew(ch); - ch = next_ch; - while (true) { - auto next_it = std::next(it); - if (next_it == widths.end() || next_it->first != it->first + 1 || - next_it->second != it->second) { - break; - } - ++it; - ch = it->first; - } - widthsArray->AddNew(ch); - widthsArray->AddNew(w); - continue; - } - // Otherwise we can have a group of the form c [w1 w2 ...]: c has width - // w1, c+1 has width w2, etc. - widthsArray->AddNew(ch); - auto curWidthArray = pdfium::MakeUnique(); - curWidthArray->AddNew(w); - curWidthArray->AddNew(next_w); - while (true) { - auto next_it = std::next(it); - if (next_it == widths.end() || next_it->first != it->first + 1) - break; - ++it; - curWidthArray->AddNew(static_cast(it->second)); - } - widthsArray->Add(std::move(curWidthArray)); - } - pCIDFont->SetNewFor("W", pDoc, widthsArray->GetObjNum()); - // TODO(npm): Support vertical writing - - auto pDescendant = pdfium::MakeUnique(); - pDescendant->AddNew(pDoc, pCIDFont->GetObjNum()); - fontDict->SetFor("DescendantFonts", std::move(pDescendant)); - CPDF_Stream* toUnicodeStream = LoadUnicode(pDoc, to_unicode); - fontDict->SetNewFor("ToUnicode", pDoc, - toUnicodeStream->GetObjNum()); - return pDoc->LoadFont(fontDict); -} - -} // namespace - -FPDF_EXPORT FPDF_PAGEOBJECT FPDF_CALLCONV -FPDFPageObj_NewTextObj(FPDF_DOCUMENT document, - FPDF_BYTESTRING font, - float font_size) { - CPDF_Document* pDoc = CPDFDocumentFromFPDFDocument(document); - if (!pDoc) - return nullptr; - - CPDF_Font* pFont = CPDF_Font::GetStockFont(pDoc, ByteStringView(font)); - if (!pFont) - return nullptr; - - auto pTextObj = pdfium::MakeUnique(); - pTextObj->m_TextState.SetFont(pFont); - pTextObj->m_TextState.SetFontSize(font_size); - pTextObj->DefaultStates(); - return pTextObj.release(); // Caller takes ownership. -} - -FPDF_EXPORT FPDF_BOOL FPDF_CALLCONV -FPDFText_SetText(FPDF_PAGEOBJECT text_object, FPDF_WIDESTRING text) { - auto* pTextObj = static_cast(text_object); - if (!pTextObj) - return false; - - size_t len = WideString::WStringLength(text); - WideString encodedText = WideString::FromUTF16LE(text, len); - ByteString byteText; - for (wchar_t wc : encodedText) { - pTextObj->GetFont()->AppendChar( - &byteText, pTextObj->GetFont()->CharCodeFromUnicode(wc)); - } - pTextObj->SetText(byteText); - return true; -} - -FPDF_EXPORT FPDF_FONT FPDF_CALLCONV FPDFText_LoadFont(FPDF_DOCUMENT document, - const uint8_t* data, - uint32_t size, - int font_type, - FPDF_BOOL cid) { - CPDF_Document* pDoc = CPDFDocumentFromFPDFDocument(document); - if (!pDoc || !data || size == 0 || - (font_type != FPDF_FONT_TYPE1 && font_type != FPDF_FONT_TRUETYPE)) { - return nullptr; - } - - auto pFont = pdfium::MakeUnique(); - - // TODO(npm): Maybe use FT_Get_X11_Font_Format to check format? Otherwise, we - // are allowing giving any font that can be loaded on freetype and setting it - // as any font type. - if (!pFont->LoadEmbedded(data, size)) - return nullptr; - - return cid ? LoadCompositeFont(pDoc, std::move(pFont), data, size, font_type) - : LoadSimpleFont(pDoc, std::move(pFont), data, size, font_type); -} - -FPDF_EXPORT FPDF_BOOL FPDF_CALLCONV -FPDFText_SetFillColor(FPDF_PAGEOBJECT text_object, - unsigned int R, - unsigned int G, - unsigned int B, - unsigned int A) { - return FPDFPageObj_SetFillColor(text_object, R, G, B, A); -} - -FPDF_EXPORT void FPDF_CALLCONV FPDFFont_Close(FPDF_FONT font) { - CPDF_Font* pFont = static_cast(font); - if (!pFont) - return; - - CPDF_Document* pDoc = pFont->GetDocument(); - if (!pDoc) - return; - - CPDF_DocPageData* pPageData = pDoc->GetPageData(); - if (!pPageData->IsForceClear()) - pPageData->ReleaseFont(pFont->GetFontDict()); -} - -FPDF_EXPORT FPDF_PAGEOBJECT FPDF_CALLCONV -FPDFPageObj_CreateTextObj(FPDF_DOCUMENT document, - FPDF_FONT font, - float font_size) { - CPDF_Document* pDoc = CPDFDocumentFromFPDFDocument(document); - CPDF_Font* pFont = static_cast(font); - if (!pDoc || !pFont) - return nullptr; - - auto pTextObj = pdfium::MakeUnique(); - pTextObj->m_TextState.SetFont(pDoc->LoadFont(pFont->GetFontDict())); - pTextObj->m_TextState.SetFontSize(font_size); - pTextObj->DefaultStates(); - return pTextObj.release(); -} diff --git a/fpdfsdk/fpdfext_embeddertest.cpp b/fpdfsdk/fpdfext_embeddertest.cpp deleted file mode 100644 index 7c28c29a8e..0000000000 --- a/fpdfsdk/fpdfext_embeddertest.cpp +++ /dev/null @@ -1,24 +0,0 @@ -// Copyright 2016 PDFium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#include "public/fpdf_ext.h" -#include "public/fpdfview.h" -#include "testing/embedder_test.h" -#include "testing/gtest/include/gtest/gtest.h" - -class FPDFExtEmbeddertest : public EmbedderTest {}; - -TEST_F(FPDFExtEmbeddertest, PageModeUnknown) { - EXPECT_EQ(PAGEMODE_UNKNOWN, FPDFDoc_GetPageMode(nullptr)); -} - -TEST_F(FPDFExtEmbeddertest, PageModeUseNone) { - EXPECT_TRUE(OpenDocument("hello_world.pdf")); - EXPECT_EQ(PAGEMODE_USENONE, FPDFDoc_GetPageMode(document())); -} - -TEST_F(FPDFExtEmbeddertest, PageModeUseOutlines) { - EXPECT_TRUE(OpenDocument("use_outlines.pdf")); - EXPECT_EQ(PAGEMODE_USEOUTLINES, FPDFDoc_GetPageMode(document())); -} diff --git a/fpdfsdk/fpdfformfill.cpp b/fpdfsdk/fpdfformfill.cpp deleted file mode 100644 index ddd460da05..0000000000 --- a/fpdfsdk/fpdfformfill.cpp +++ /dev/null @@ -1,836 +0,0 @@ -// Copyright 2014 PDFium Authors. All rights reserved. -// 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_formfill.h" - -#include -#include - -#include "core/fpdfapi/page/cpdf_page.h" -#include "core/fpdfapi/parser/cpdf_document.h" -#include "core/fpdfapi/render/cpdf_renderoptions.h" -#include "core/fpdfdoc/cpdf_formcontrol.h" -#include "core/fpdfdoc/cpdf_formfield.h" -#include "core/fpdfdoc/cpdf_interform.h" -#include "core/fpdfdoc/cpdf_occontext.h" -#include "core/fxge/cfx_defaultrenderdevice.h" -#include "fpdfsdk/cpdfsdk_actionhandler.h" -#include "fpdfsdk/cpdfsdk_formfillenvironment.h" -#include "fpdfsdk/cpdfsdk_helpers.h" -#include "fpdfsdk/cpdfsdk_interform.h" -#include "fpdfsdk/cpdfsdk_pageview.h" -#include "public/fpdfview.h" -#include "third_party/base/ptr_util.h" -#include "third_party/base/stl_util.h" - -#ifdef PDF_ENABLE_XFA -#include "fpdfsdk/fpdfxfa/cpdfxfa_context.h" -#include "fpdfsdk/fpdfxfa/cpdfxfa_page.h" -#include "xfa/fxfa/cxfa_ffdocview.h" -#include "xfa/fxfa/cxfa_ffpageview.h" -#include "xfa/fxfa/cxfa_ffwidget.h" - -static_assert(static_cast(FormType::kNone) == FORMTYPE_NONE, - "None form types must match"); -static_assert(static_cast(FormType::kAcroForm) == FORMTYPE_ACRO_FORM, - "AcroForm form types must match"); -static_assert(static_cast(FormType::kXFAFull) == FORMTYPE_XFA_FULL, - "XFA full form types must match"); -static_assert(static_cast(FormType::kXFAForeground) == - FORMTYPE_XFA_FOREGROUND, - "XFA foreground form types must match"); -#endif // PDF_ENABLE_XFA - -static_assert(static_cast(FormFieldType::kUnknown) == - FPDF_FORMFIELD_UNKNOWN, - "Unknown form field types must match"); -static_assert(static_cast(FormFieldType::kPushButton) == - FPDF_FORMFIELD_PUSHBUTTON, - "PushButton form field types must match"); -static_assert(static_cast(FormFieldType::kCheckBox) == - FPDF_FORMFIELD_CHECKBOX, - "CheckBox form field types must match"); -static_assert(static_cast(FormFieldType::kRadioButton) == - FPDF_FORMFIELD_RADIOBUTTON, - "RadioButton form field types must match"); -static_assert(static_cast(FormFieldType::kComboBox) == - FPDF_FORMFIELD_COMBOBOX, - "ComboBox form field types must match"); -static_assert(static_cast(FormFieldType::kListBox) == - FPDF_FORMFIELD_LISTBOX, - "ListBox form field types must match"); -static_assert(static_cast(FormFieldType::kTextField) == - FPDF_FORMFIELD_TEXTFIELD, - "TextField form field types must match"); -static_assert(static_cast(FormFieldType::kSignature) == - FPDF_FORMFIELD_SIGNATURE, - "Signature form field types must match"); -#ifdef PDF_ENABLE_XFA -static_assert(static_cast(FormFieldType::kXFA) == FPDF_FORMFIELD_XFA, - "XFA form field types must match"); -static_assert(static_cast(FormFieldType::kXFA_CheckBox) == - FPDF_FORMFIELD_XFA_CHECKBOX, - "XFA CheckBox form field types must match"); -static_assert(static_cast(FormFieldType::kXFA_ComboBox) == - FPDF_FORMFIELD_XFA_COMBOBOX, - "XFA ComboBox form field types must match"); -static_assert(static_cast(FormFieldType::kXFA_ImageField) == - FPDF_FORMFIELD_XFA_IMAGEFIELD, - "XFA ImageField form field types must match"); -static_assert(static_cast(FormFieldType::kXFA_ListBox) == - FPDF_FORMFIELD_XFA_LISTBOX, - "XFA ListBox form field types must match"); -static_assert(static_cast(FormFieldType::kXFA_PushButton) == - FPDF_FORMFIELD_XFA_PUSHBUTTON, - "XFA PushButton form field types must match"); -static_assert(static_cast(FormFieldType::kXFA_Signature) == - FPDF_FORMFIELD_XFA_SIGNATURE, - "XFA Signature form field types must match"); -static_assert(static_cast(FormFieldType::kXFA_TextField) == - FPDF_FORMFIELD_XFA_TEXTFIELD, - "XFA TextField form field types must match"); -#endif // PDF_ENABLE_XFA -static_assert(kFormFieldTypeCount == FPDF_FORMFIELD_COUNT, - "Number of form field types must match"); - -namespace { - -CPDFSDK_FormFillEnvironment* HandleToCPDFSDKEnvironment( - FPDF_FORMHANDLE handle) { - return static_cast(handle); -} - -CPDFSDK_InterForm* FormHandleToInterForm(FPDF_FORMHANDLE hHandle) { - CPDFSDK_FormFillEnvironment* pFormFillEnv = - HandleToCPDFSDKEnvironment(hHandle); - return pFormFillEnv ? pFormFillEnv->GetInterForm() : nullptr; -} - -CPDFSDK_PageView* FormHandleToPageView(FPDF_FORMHANDLE hHandle, - FPDF_PAGE page) { - UnderlyingPageType* pPage = UnderlyingFromFPDFPage(page); - if (!pPage) - return nullptr; - - CPDFSDK_FormFillEnvironment* pFormFillEnv = - HandleToCPDFSDKEnvironment(hHandle); - return pFormFillEnv ? pFormFillEnv->GetPageView(pPage, true) : nullptr; -} - -#ifdef PDF_ENABLE_XFA -std::vector* FromFPDFStringHandle(FPDF_STRINGHANDLE handle) { - return static_cast*>(handle); -} - -FPDF_STRINGHANDLE ToFPDFStringHandle(std::vector* strings) { - return static_cast(strings); -} -#endif // PDF_ENABLE_XFA - -void FFLCommon(FPDF_FORMHANDLE hHandle, - FPDF_BITMAP bitmap, - FPDF_RECORDER recorder, - FPDF_PAGE page, - int start_x, - int start_y, - int size_x, - int size_y, - int rotate, - int flags) { - if (!hHandle) - return; - - UnderlyingPageType* pPage = UnderlyingFromFPDFPage(page); - if (!pPage) - return; - -#ifdef PDF_ENABLE_XFA - CPDFXFA_Context* pContext = pPage->GetContext(); - if (!pContext) - return; - CPDF_Document* pPDFDoc = pContext->GetPDFDoc(); - if (!pPDFDoc) - return; - CPDFSDK_FormFillEnvironment* pFormFillEnv = - HandleToCPDFSDKEnvironment(hHandle); - if (!pFormFillEnv) - return; -#endif // PDF_ENABLE_XFA - - CFX_Matrix matrix = - pPage->GetDisplayMatrix(start_x, start_y, size_x, size_y, rotate); - FX_RECT clip(start_x, start_y, start_x + size_x, start_y + size_y); - - auto pDevice = pdfium::MakeUnique(); -#ifdef _SKIA_SUPPORT_ - pDevice->AttachRecorder(static_cast(recorder)); -#endif - RetainPtr holder(CFXBitmapFromFPDFBitmap(bitmap)); - pDevice->Attach(holder, false, nullptr, false); - { - CFX_RenderDevice::StateRestorer restorer(pDevice.get()); - pDevice->SetClip_Rect(clip); - - CPDF_RenderOptions options; - uint32_t option_flags = options.GetFlags(); - if (flags & FPDF_LCD_TEXT) - option_flags |= RENDER_CLEARTYPE; - else - option_flags &= ~RENDER_CLEARTYPE; - options.SetFlags(option_flags); - - // Grayscale output - if (flags & FPDF_GRAYSCALE) - options.SetColorMode(CPDF_RenderOptions::kGray); - - options.SetDrawAnnots(flags & FPDF_ANNOT); - -#ifdef PDF_ENABLE_XFA - options.SetOCContext( - pdfium::MakeRetain(pPDFDoc, CPDF_OCContext::View)); - if (CPDFSDK_PageView* pPageView = pFormFillEnv->GetPageView(pPage, true)) - pPageView->PageView_OnDraw(pDevice.get(), &matrix, &options, clip); -#else // PDF_ENABLE_XFA - options.SetOCContext(pdfium::MakeRetain( - pPage->m_pDocument.Get(), CPDF_OCContext::View)); - if (CPDFSDK_PageView* pPageView = FormHandleToPageView(hHandle, pPage)) - pPageView->PageView_OnDraw(pDevice.get(), &matrix, &options); -#endif // PDF_ENABLE_XFA - } -#ifdef _SKIA_SUPPORT_PATHS_ - pDevice->Flush(true); - holder->UnPreMultiply(); -#endif -} - -} // namespace - -FPDF_EXPORT int FPDF_CALLCONV -FPDFPage_HasFormFieldAtPoint(FPDF_FORMHANDLE hHandle, - FPDF_PAGE page, - double page_x, - double page_y) { - if (!hHandle) - return -1; - CPDF_Page* pPage = CPDFPageFromFPDFPage(page); - if (pPage) { - CPDF_InterForm interform(pPage->m_pDocument.Get()); - CPDF_FormControl* pFormCtrl = interform.GetControlAtPoint( - pPage, - CFX_PointF(static_cast(page_x), static_cast(page_y)), - nullptr); - if (!pFormCtrl) - return -1; - CPDF_FormField* pFormField = pFormCtrl->GetField(); - return pFormField ? static_cast(pFormField->GetFieldType()) : -1; - } - -#ifdef PDF_ENABLE_XFA - CPDFXFA_Page* pXFAPage = UnderlyingFromFPDFPage(page); - if (!pXFAPage) - return -1; - - CXFA_FFPageView* pPageView = pXFAPage->GetXFAPageView(); - if (!pPageView) - return -1; - - CXFA_FFDocView* pDocView = pPageView->GetDocView(); - if (!pDocView) - return -1; - - CXFA_FFWidgetHandler* pWidgetHandler = pDocView->GetWidgetHandler(); - if (!pWidgetHandler) - return -1; - - std::unique_ptr pWidgetIterator( - pPageView->CreateWidgetIterator(XFA_TRAVERSEWAY_Form, - XFA_WidgetStatus_Viewable)); - if (!pWidgetIterator) - return -1; - - CXFA_FFWidget* pXFAAnnot; - while ((pXFAAnnot = pWidgetIterator->MoveToNext()) != nullptr) { - if (pXFAAnnot->GetFormFieldType() == FormFieldType::kXFA) - continue; - - CFX_RectF rcBBox = pXFAAnnot->GetWidgetRect(); - CFX_FloatRect rcWidget(rcBBox.left, rcBBox.top, rcBBox.left + rcBBox.width, - rcBBox.top + rcBBox.height); - rcWidget.Inflate(1.0f, 1.0f); - if (rcWidget.Contains(CFX_PointF(static_cast(page_x), - static_cast(page_y)))) { - return static_cast(pXFAAnnot->GetFormFieldType()); - } - } -#endif // PDF_ENABLE_XFA - return -1; -} - -FPDF_EXPORT int FPDF_CALLCONV -FPDFPage_FormFieldZOrderAtPoint(FPDF_FORMHANDLE hHandle, - FPDF_PAGE page, - double page_x, - double page_y) { - if (!hHandle) - return -1; - CPDF_Page* pPage = CPDFPageFromFPDFPage(page); - if (!pPage) - return -1; - CPDF_InterForm interform(pPage->m_pDocument.Get()); - int z_order = -1; - (void)interform.GetControlAtPoint( - pPage, CFX_PointF(static_cast(page_x), static_cast(page_y)), - &z_order); - return z_order; -} - -FPDF_EXPORT FPDF_FORMHANDLE FPDF_CALLCONV -FPDFDOC_InitFormFillEnvironment(FPDF_DOCUMENT document, - FPDF_FORMFILLINFO* formInfo) { -#ifdef PDF_ENABLE_XFA - const int kRequiredVersion = 2; -#else // PDF_ENABLE_XFA - const int kRequiredVersion = 1; -#endif // PDF_ENABLE_XFA - if (!formInfo || formInfo->version != kRequiredVersion) - return nullptr; - - UnderlyingDocumentType* pDocument = UnderlyingFromFPDFDocument(document); - if (!pDocument) - return nullptr; - -#ifdef PDF_ENABLE_XFA - // If the CPDFXFA_Context has a FormFillEnvironment already then we've done - // this and can just return the old Env. Otherwise, we'll end up setting a new - // environment into the XFADocument and, that could get weird. - if (pDocument->GetFormFillEnv()) - return pDocument->GetFormFillEnv(); -#endif - - auto pFormFillEnv = - pdfium::MakeUnique(pDocument, formInfo); - -#ifdef PDF_ENABLE_XFA - pDocument->SetFormFillEnv(pFormFillEnv.get()); -#endif // PDF_ENABLE_XFA - - return pFormFillEnv.release(); // Caller takes ownership. -} - -FPDF_EXPORT void FPDF_CALLCONV -FPDFDOC_ExitFormFillEnvironment(FPDF_FORMHANDLE hHandle) { - CPDFSDK_FormFillEnvironment* pFormFillEnv = - HandleToCPDFSDKEnvironment(hHandle); - if (!pFormFillEnv) - return; - -#ifdef PDF_ENABLE_XFA - // Reset the focused annotations and remove the SDK document from the - // XFA document. - pFormFillEnv->ClearAllFocusedAnnots(); - // If the document was closed first, it's possible the XFA document - // is now a nullptr. - if (pFormFillEnv->GetXFAContext()) - pFormFillEnv->GetXFAContext()->SetFormFillEnv(nullptr); -#endif // PDF_ENABLE_XFA - delete pFormFillEnv; -} - -FPDF_EXPORT FPDF_BOOL FPDF_CALLCONV FORM_OnMouseMove(FPDF_FORMHANDLE hHandle, - FPDF_PAGE page, - int modifier, - double page_x, - double page_y) { - CPDFSDK_PageView* pPageView = FormHandleToPageView(hHandle, page); - if (!pPageView) - return false; - return pPageView->OnMouseMove(CFX_PointF(page_x, page_y), modifier); -} - -FPDF_EXPORT FPDF_BOOL FPDF_CALLCONV FORM_OnFocus(FPDF_FORMHANDLE hHandle, - FPDF_PAGE page, - int modifier, - double page_x, - double page_y) { - CPDFSDK_PageView* pPageView = FormHandleToPageView(hHandle, page); - if (!pPageView) - return false; - return pPageView->OnFocus(CFX_PointF(page_x, page_y), modifier); -} - -FPDF_EXPORT FPDF_BOOL FPDF_CALLCONV FORM_OnLButtonDown(FPDF_FORMHANDLE hHandle, - FPDF_PAGE page, - int modifier, - double page_x, - double page_y) { - CPDFSDK_PageView* pPageView = FormHandleToPageView(hHandle, page); - if (!pPageView) - return false; - return pPageView->OnLButtonDown(CFX_PointF(page_x, page_y), modifier); -} - -FPDF_EXPORT FPDF_BOOL FPDF_CALLCONV FORM_OnLButtonUp(FPDF_FORMHANDLE hHandle, - FPDF_PAGE page, - int modifier, - double page_x, - double page_y) { - CPDFSDK_PageView* pPageView = FormHandleToPageView(hHandle, page); - if (!pPageView) - return false; - return pPageView->OnLButtonUp(CFX_PointF(page_x, page_y), modifier); -} - -#ifdef PDF_ENABLE_XFA -FPDF_EXPORT FPDF_BOOL FPDF_CALLCONV FORM_OnRButtonDown(FPDF_FORMHANDLE hHandle, - FPDF_PAGE page, - int modifier, - double page_x, - double page_y) { - CPDFSDK_PageView* pPageView = FormHandleToPageView(hHandle, page); - if (!pPageView) - return false; - return pPageView->OnRButtonDown(CFX_PointF(page_x, page_y), modifier); -} - -FPDF_EXPORT FPDF_BOOL FPDF_CALLCONV FORM_OnRButtonUp(FPDF_FORMHANDLE hHandle, - FPDF_PAGE page, - int modifier, - double page_x, - double page_y) { - CPDFSDK_PageView* pPageView = FormHandleToPageView(hHandle, page); - if (!pPageView) - return false; - return pPageView->OnRButtonUp(CFX_PointF(page_x, page_y), modifier); -} -#endif // PDF_ENABLE_XFA - -FPDF_EXPORT FPDF_BOOL FPDF_CALLCONV FORM_OnKeyDown(FPDF_FORMHANDLE hHandle, - FPDF_PAGE page, - int nKeyCode, - int modifier) { - CPDFSDK_PageView* pPageView = FormHandleToPageView(hHandle, page); - if (!pPageView) - return false; - return pPageView->OnKeyDown(nKeyCode, modifier); -} - -FPDF_EXPORT FPDF_BOOL FPDF_CALLCONV FORM_OnKeyUp(FPDF_FORMHANDLE hHandle, - FPDF_PAGE page, - int nKeyCode, - int modifier) { - CPDFSDK_PageView* pPageView = FormHandleToPageView(hHandle, page); - if (!pPageView) - return false; - return pPageView->OnKeyUp(nKeyCode, modifier); -} - -FPDF_EXPORT FPDF_BOOL FPDF_CALLCONV FORM_OnChar(FPDF_FORMHANDLE hHandle, - FPDF_PAGE page, - int nChar, - int modifier) { - CPDFSDK_PageView* pPageView = FormHandleToPageView(hHandle, page); - if (!pPageView) - return false; - return pPageView->OnChar(nChar, modifier); -} - -FPDF_EXPORT unsigned long FPDF_CALLCONV -FORM_GetSelectedText(FPDF_FORMHANDLE hHandle, - FPDF_PAGE page, - void* buffer, - unsigned long buflen) { - CPDFSDK_PageView* pPageView = FormHandleToPageView(hHandle, page); - if (!pPageView) - return 0; - - WideString wide_str_form_text = pPageView->GetSelectedText(); - ByteString encoded_form_text = wide_str_form_text.UTF16LE_Encode(); - unsigned long form_text_len = encoded_form_text.GetLength(); - - if (buffer && buflen >= form_text_len) - memcpy(buffer, encoded_form_text.c_str(), form_text_len); - - return form_text_len; -} - -FPDF_EXPORT void FPDF_CALLCONV FORM_ReplaceSelection(FPDF_FORMHANDLE hHandle, - FPDF_PAGE page, - FPDF_WIDESTRING wsText) { - CPDFSDK_PageView* pPageView = FormHandleToPageView(hHandle, page); - if (!pPageView) - return; - - size_t len = WideString::WStringLength(wsText); - WideString wide_str_text = WideString::FromUTF16LE(wsText, len); - - pPageView->ReplaceSelection(wide_str_text); -} - -FPDF_EXPORT FPDF_BOOL FPDF_CALLCONV -FORM_ForceToKillFocus(FPDF_FORMHANDLE hHandle) { - CPDFSDK_FormFillEnvironment* pFormFillEnv = - HandleToCPDFSDKEnvironment(hHandle); - if (!pFormFillEnv) - return false; - return pFormFillEnv->KillFocusAnnot(0); -} - -FPDF_EXPORT void FPDF_CALLCONV FPDF_FFLDraw(FPDF_FORMHANDLE hHandle, - FPDF_BITMAP bitmap, - FPDF_PAGE page, - int start_x, - int start_y, - int size_x, - int size_y, - int rotate, - int flags) { - FFLCommon(hHandle, bitmap, nullptr, page, start_x, start_y, size_x, size_y, - rotate, flags); -} - -#ifdef _SKIA_SUPPORT_ -FPDF_EXPORT void FPDF_CALLCONV FPDF_FFLRecord(FPDF_FORMHANDLE hHandle, - FPDF_RECORDER recorder, - FPDF_PAGE page, - int start_x, - int start_y, - int size_x, - int size_y, - int rotate, - int flags) { - FFLCommon(hHandle, nullptr, recorder, page, start_x, start_y, size_x, size_y, - rotate, flags); -} -#endif - -#ifdef PDF_ENABLE_XFA -FPDF_EXPORT void FPDF_CALLCONV FPDF_Widget_Undo(FPDF_DOCUMENT document, - FPDF_WIDGET hWidget) { - if (!hWidget || !document) - return; - - CPDFXFA_Context* pContext = static_cast(document); - if (!pContext->ContainsXFAForm()) - return; - - static_cast(hWidget)->Undo(); -} - -FPDF_EXPORT void FPDF_CALLCONV FPDF_Widget_Redo(FPDF_DOCUMENT document, - FPDF_WIDGET hWidget) { - if (!hWidget || !document) - return; - - CPDFXFA_Context* pContext = static_cast(document); - if (!pContext->ContainsXFAForm()) - return; - - static_cast(hWidget)->Redo(); -} - -FPDF_EXPORT void FPDF_CALLCONV FPDF_Widget_SelectAll(FPDF_DOCUMENT document, - FPDF_WIDGET hWidget) { - if (!hWidget || !document) - return; - - CPDFXFA_Context* pContext = static_cast(document); - if (!pContext->ContainsXFAForm()) - return; - - static_cast(hWidget)->SelectAll(); -} - -FPDF_EXPORT void FPDF_CALLCONV FPDF_Widget_Copy(FPDF_DOCUMENT document, - FPDF_WIDGET hWidget, - FPDF_WIDESTRING wsText, - FPDF_DWORD* size) { - if (!hWidget || !document) - return; - - CPDFXFA_Context* pContext = static_cast(document); - if (!pContext->ContainsXFAForm()) - return; - - WideString wsCpText = - static_cast(hWidget)->Copy().value_or(WideString()); - - ByteString bsCpText = wsCpText.UTF16LE_Encode(); - uint32_t len = bsCpText.GetLength() / sizeof(unsigned short); - if (!wsText) { - *size = len; - return; - } - - uint32_t real_size = len < *size ? len : *size; - if (real_size > 0) { - memcpy((void*)wsText, - bsCpText.GetBuffer(real_size * sizeof(unsigned short)), - real_size * sizeof(unsigned short)); - bsCpText.ReleaseBuffer(real_size * sizeof(unsigned short)); - } - *size = real_size; -} - -FPDF_EXPORT void FPDF_CALLCONV FPDF_Widget_Cut(FPDF_DOCUMENT document, - FPDF_WIDGET hWidget, - FPDF_WIDESTRING wsText, - FPDF_DWORD* size) { - if (!hWidget || !document) - return; - - CPDFXFA_Context* pContext = static_cast(document); - if (!pContext->ContainsXFAForm()) - return; - - WideString wsCpText = - static_cast(hWidget)->Cut().value_or(WideString()); - - ByteString bsCpText = wsCpText.UTF16LE_Encode(); - uint32_t len = bsCpText.GetLength() / sizeof(unsigned short); - if (!wsText) { - *size = len; - return; - } - - uint32_t real_size = len < *size ? len : *size; - if (real_size > 0) { - memcpy((void*)wsText, - bsCpText.GetBuffer(real_size * sizeof(unsigned short)), - real_size * sizeof(unsigned short)); - bsCpText.ReleaseBuffer(real_size * sizeof(unsigned short)); - } - *size = real_size; -} - -FPDF_EXPORT void FPDF_CALLCONV FPDF_Widget_Paste(FPDF_DOCUMENT document, - FPDF_WIDGET hWidget, - FPDF_WIDESTRING wsText, - FPDF_DWORD size) { - if (!hWidget || !document) - return; - - CPDFXFA_Context* pContext = static_cast(document); - if (!pContext->ContainsXFAForm()) - return; - - WideString wstr = WideString::FromUTF16LE(wsText, size); - static_cast(hWidget)->Paste(wstr); -} - -FPDF_EXPORT void FPDF_CALLCONV -FPDF_Widget_ReplaceSpellCheckWord(FPDF_DOCUMENT document, - FPDF_WIDGET hWidget, - float x, - float y, - FPDF_BYTESTRING bsText) { - if (!hWidget || !document) - return; - - CPDFXFA_Context* pContext = static_cast(document); - if (!pContext->ContainsXFAForm()) - return; - - CFX_PointF ptPopup; - ptPopup.x = x; - ptPopup.y = y; - ByteStringView bs(bsText); - static_cast(hWidget)->ReplaceSpellCheckWord(ptPopup, bs); -} - -FPDF_EXPORT void FPDF_CALLCONV -FPDF_Widget_GetSpellCheckWords(FPDF_DOCUMENT document, - FPDF_WIDGET hWidget, - float x, - float y, - FPDF_STRINGHANDLE* stringHandle) { - if (!hWidget || !document) - return; - - auto* pContext = static_cast(document); - if (!pContext->ContainsXFAForm()) - return; - - CFX_PointF ptPopup; - ptPopup.x = x; - ptPopup.y = y; - auto sSuggestWords = pdfium::MakeUnique>(); - static_cast(hWidget)->GetSuggestWords(ptPopup, - sSuggestWords.get()); - - // Caller takes ownership. - *stringHandle = ToFPDFStringHandle(sSuggestWords.release()); -} - -FPDF_EXPORT int FPDF_CALLCONV -FPDF_StringHandleCounts(FPDF_STRINGHANDLE sHandle) { - std::vector* sSuggestWords = FromFPDFStringHandle(sHandle); - return sSuggestWords ? pdfium::CollectionSize(*sSuggestWords) : -1; -} - -FPDF_EXPORT FPDF_BOOL FPDF_CALLCONV -FPDF_StringHandleGetStringByIndex(FPDF_STRINGHANDLE sHandle, - int index, - FPDF_BYTESTRING bsText, - FPDF_DWORD* size) { - if (!sHandle || !size) - return false; - - int count = FPDF_StringHandleCounts(sHandle); - if (index < 0 || index >= count) - return false; - - std::vector* sSuggestWords = FromFPDFStringHandle(sHandle); - uint32_t len = (*sSuggestWords)[index].GetLength(); - if (!bsText) { - *size = len; - return true; - } - - uint32_t real_size = len < *size ? len : *size; - if (real_size > 0) - memcpy((void*)bsText, (*sSuggestWords)[index].c_str(), real_size); - *size = real_size; - return true; -} - -FPDF_EXPORT void FPDF_CALLCONV -FPDF_StringHandleRelease(FPDF_STRINGHANDLE stringHandle) { - delete FromFPDFStringHandle(stringHandle); -} - -FPDF_EXPORT FPDF_BOOL FPDF_CALLCONV -FPDF_StringHandleAddString(FPDF_STRINGHANDLE stringHandle, - FPDF_BYTESTRING bsText, - FPDF_DWORD size) { - if (!stringHandle || !bsText || size == 0) - return false; - - FromFPDFStringHandle(stringHandle)->push_back(ByteString(bsText, size)); - return true; -} -#endif // PDF_ENABLE_XFA - -FPDF_EXPORT void FPDF_CALLCONV -FPDF_SetFormFieldHighlightColor(FPDF_FORMHANDLE hHandle, - int fieldType, - unsigned long color) { - CPDFSDK_InterForm* interForm = FormHandleToInterForm(hHandle); - if (!interForm) - return; - - Optional cast_input = IntToFormFieldType(fieldType); - if (!cast_input) - return; - - if (cast_input.value() == FormFieldType::kUnknown) { - interForm->SetAllHighlightColors(color); - } else { - interForm->SetHighlightColor(color, cast_input.value()); - } -} - -FPDF_EXPORT void FPDF_CALLCONV -FPDF_SetFormFieldHighlightAlpha(FPDF_FORMHANDLE hHandle, unsigned char alpha) { - if (CPDFSDK_InterForm* pInterForm = FormHandleToInterForm(hHandle)) - pInterForm->SetHighlightAlpha(alpha); -} - -FPDF_EXPORT void FPDF_CALLCONV -FPDF_RemoveFormFieldHighlight(FPDF_FORMHANDLE hHandle) { - if (CPDFSDK_InterForm* pInterForm = FormHandleToInterForm(hHandle)) - pInterForm->RemoveAllHighLights(); -} - -FPDF_EXPORT void FPDF_CALLCONV FORM_OnAfterLoadPage(FPDF_PAGE page, - FPDF_FORMHANDLE hHandle) { - if (CPDFSDK_PageView* pPageView = FormHandleToPageView(hHandle, page)) - pPageView->SetValid(true); -} - -FPDF_EXPORT void FPDF_CALLCONV FORM_OnBeforeClosePage(FPDF_PAGE page, - FPDF_FORMHANDLE hHandle) { - CPDFSDK_FormFillEnvironment* pFormFillEnv = - HandleToCPDFSDKEnvironment(hHandle); - if (!pFormFillEnv) - return; - - UnderlyingPageType* pPage = UnderlyingFromFPDFPage(page); - if (!pPage) - return; - - CPDFSDK_PageView* pPageView = pFormFillEnv->GetPageView(pPage, false); - if (pPageView) { - pPageView->SetValid(false); - // RemovePageView() takes care of the delete for us. - pFormFillEnv->RemovePageView(pPage); - } -} - -FPDF_EXPORT void FPDF_CALLCONV -FORM_DoDocumentJSAction(FPDF_FORMHANDLE hHandle) { - CPDFSDK_FormFillEnvironment* pFormFillEnv = - HandleToCPDFSDKEnvironment(hHandle); - if (pFormFillEnv && pFormFillEnv->IsJSPlatformPresent()) - pFormFillEnv->ProcJavascriptFun(); -} - -FPDF_EXPORT void FPDF_CALLCONV -FORM_DoDocumentOpenAction(FPDF_FORMHANDLE hHandle) { - CPDFSDK_FormFillEnvironment* pFormFillEnv = - HandleToCPDFSDKEnvironment(hHandle); - if (pFormFillEnv && pFormFillEnv->IsJSPlatformPresent()) - pFormFillEnv->ProcOpenAction(); -} - -FPDF_EXPORT void FPDF_CALLCONV FORM_DoDocumentAAction(FPDF_FORMHANDLE hHandle, - int aaType) { - CPDFSDK_FormFillEnvironment* pFormFillEnv = - HandleToCPDFSDKEnvironment(hHandle); - if (!pFormFillEnv) - return; - - CPDF_Document* pDoc = pFormFillEnv->GetPDFDocument(); - const CPDF_Dictionary* pDict = pDoc->GetRoot(); - if (!pDict) - return; - - CPDF_AAction aa(pDict->GetDictFor("AA")); - auto type = static_cast(aaType); - if (aa.ActionExist(type)) { - CPDF_Action action = aa.GetAction(type); - CPDFSDK_ActionHandler* pActionHandler = - HandleToCPDFSDKEnvironment(hHandle)->GetActionHandler(); - pActionHandler->DoAction_Document(action, type, pFormFillEnv); - } -} - -FPDF_EXPORT void FPDF_CALLCONV FORM_DoPageAAction(FPDF_PAGE page, - FPDF_FORMHANDLE hHandle, - int aaType) { - CPDFSDK_FormFillEnvironment* pFormFillEnv = - HandleToCPDFSDKEnvironment(hHandle); - if (!pFormFillEnv) - return; - - UnderlyingPageType* pPage = UnderlyingFromFPDFPage(page); - CPDF_Page* pPDFPage = CPDFPageFromFPDFPage(page); - if (!pPDFPage) - return; - - if (!pFormFillEnv->GetPageView(pPage, false)) - return; - - CPDFSDK_ActionHandler* pActionHandler = pFormFillEnv->GetActionHandler(); - CPDF_Dictionary* pPageDict = pPDFPage->m_pFormDict.Get(); - CPDF_AAction aa(pPageDict->GetDictFor("AA")); - CPDF_AAction::AActionType type = aaType == FPDFPAGE_AACTION_OPEN - ? CPDF_AAction::OpenPage - : CPDF_AAction::ClosePage; - if (aa.ActionExist(type)) { - CPDF_Action action = aa.GetAction(type); - pActionHandler->DoAction_Page(action, type, pFormFillEnv); - } -} diff --git a/fpdfsdk/fpdfformfill_embeddertest.cpp b/fpdfsdk/fpdfformfill_embeddertest.cpp deleted file mode 100644 index 709bc39bfa..0000000000 --- a/fpdfsdk/fpdfformfill_embeddertest.cpp +++ /dev/null @@ -1,1377 +0,0 @@ -// Copyright 2015 PDFium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#include -#include -#include - -#include "core/fxcrt/fx_coordinates.h" -#include "core/fxcrt/fx_string.h" -#include "core/fxcrt/fx_system.h" -#include "public/cpp/fpdf_deleters.h" -#include "public/fpdf_formfill.h" -#include "public/fpdf_fwlevent.h" -#include "testing/embedder_test.h" -#include "testing/embedder_test_mock_delegate.h" -#include "testing/embedder_test_timer_handling_delegate.h" -#include "testing/gmock/include/gmock/gmock.h" -#include "testing/gtest/include/gtest/gtest.h" - -using testing::_; - -using FPDFFormFillEmbeddertest = EmbedderTest; - -// A base class for many related tests that involve clicking and typing into -// form fields. -class FPDFFormFillInteractiveEmbeddertest : public FPDFFormFillEmbeddertest { - protected: - FPDFFormFillInteractiveEmbeddertest() = default; - ~FPDFFormFillInteractiveEmbeddertest() override = default; - - void SetUp() override { - FPDFFormFillEmbeddertest::SetUp(); - ASSERT_TRUE(OpenDocument(GetDocumentName())); - page_ = LoadPage(0); - ASSERT_TRUE(page_); - FormSanityChecks(); - } - - void TearDown() override { - UnloadPage(page_); - FPDFFormFillEmbeddertest::TearDown(); - } - - // Returns the name of the PDF to use. - virtual const char* GetDocumentName() const = 0; - - // Returns the type of field(s) in the PDF. - virtual int GetFormType() const = 0; - - // Optionally do some sanity check on the document after loading. - virtual void FormSanityChecks() {} - - FPDF_PAGE page() { return page_; } - - int GetFormTypeAtPoint(const CFX_PointF& point) { - return FPDFPage_HasFormFieldAtPoint(form_handle(), page_, point.x, point.y); - } - - void ClickOnFormFieldAtPoint(const CFX_PointF& point) { - // Click on the text field or combobox as specified by coordinates. - FORM_OnMouseMove(form_handle(), page_, 0, point.x, point.y); - FORM_OnLButtonDown(form_handle(), page_, 0, point.x, point.y); - FORM_OnLButtonUp(form_handle(), page_, 0, point.x, point.y); - } - - void TypeTextIntoTextField(int num_chars, const CFX_PointF& point) { - EXPECT_EQ(GetFormType(), GetFormTypeAtPoint(point)); - ClickOnFormFieldAtPoint(point); - - // Type text starting with 'A' to as many chars as specified by |num_chars|. - for (int i = 0; i < num_chars; ++i) { - FORM_OnChar(form_handle(), page_, 'A' + i, 0); - } - } - - // Navigates to text field using the mouse and then selects text via the - // shift and specfied left or right arrow key. - void SelectTextWithKeyboard(int num_chars, - int arrow_key, - const CFX_PointF& point) { - // Navigate to starting position for selection. - ClickOnFormFieldAtPoint(point); - - // Hold down shift (and don't release until entire text is selected). - FORM_OnKeyDown(form_handle(), page_, FWL_VKEY_Shift, 0); - - // Select text char by char via left or right arrow key. - for (int i = 0; i < num_chars; ++i) { - FORM_OnKeyDown(form_handle(), page_, arrow_key, FWL_EVENTFLAG_ShiftKey); - FORM_OnKeyUp(form_handle(), page_, arrow_key, FWL_EVENTFLAG_ShiftKey); - } - FORM_OnKeyUp(form_handle(), page_, FWL_VKEY_Shift, 0); - } - - // Uses the mouse to navigate to text field and select text. - void SelectTextWithMouse(const CFX_PointF& start, const CFX_PointF& end) { - ASSERT(start.y == end.y); - - // Navigate to starting position and click mouse. - FORM_OnMouseMove(form_handle(), page_, 0, start.x, start.y); - FORM_OnLButtonDown(form_handle(), page_, 0, start.x, start.y); - - // Hold down mouse until reach end of desired selection. - FORM_OnMouseMove(form_handle(), page_, 0, end.x, end.y); - FORM_OnLButtonUp(form_handle(), page_, 0, end.x, end.y); - } - - void CheckSelection(const WideStringView& expected_string) { - // Calculate expected length for selected text. - int num_chars = expected_string.GetLength(); - - // Check actual selection against expected selection. - const unsigned long expected_length = - sizeof(unsigned short) * (num_chars + 1); - unsigned long sel_text_len = - FORM_GetSelectedText(form_handle(), page_, nullptr, 0); - ASSERT_EQ(expected_length, sel_text_len); - - std::vector buf(sel_text_len); - EXPECT_EQ(expected_length, FORM_GetSelectedText(form_handle(), page_, - buf.data(), sel_text_len)); - - EXPECT_EQ(expected_string, WideString::FromUTF16LE(buf.data(), num_chars)); - } - - private: - FPDF_PAGE page_ = nullptr; -}; - -class FPDFFormFillTextFormEmbeddertest - : public FPDFFormFillInteractiveEmbeddertest { - protected: - FPDFFormFillTextFormEmbeddertest() = default; - ~FPDFFormFillTextFormEmbeddertest() override = default; - - const char* GetDocumentName() const override { - // PDF with several form text fields: - // - "Text Box" - Regular text box with no special attributes. - // - "ReadOnly" - Ff: 1. - // - "CharLimit" - MaxLen: 10, V: Elephant. - return "text_form_multiple.pdf"; - } - - int GetFormType() const override { return FPDF_FORMFIELD_TEXTFIELD; } - - void FormSanityChecks() override { - EXPECT_EQ(GetFormType(), GetFormTypeAtPoint(CharLimitFormBegin())); - EXPECT_EQ(GetFormType(), GetFormTypeAtPoint(CharLimitFormEnd())); - EXPECT_EQ(GetFormType(), GetFormTypeAtPoint(RegularFormBegin())); - EXPECT_EQ(GetFormType(), GetFormTypeAtPoint(RegularFormEnd())); - } - - void SelectAllCharLimitFormTextWithMouse() { - SelectTextWithMouse(CharLimitFormEnd(), CharLimitFormBegin()); - } - - void SelectAllRegularFormTextWithMouse() { - SelectTextWithMouse(RegularFormEnd(), RegularFormBegin()); - } - - const CFX_PointF& CharLimitFormBegin() const { - static const CFX_PointF point = CharLimitFormAtX(kFormBeginX); - return point; - } - - const CFX_PointF& CharLimitFormEnd() const { - static const CFX_PointF point = CharLimitFormAtX(kFormEndX); - return point; - } - - const CFX_PointF& RegularFormBegin() const { - static const CFX_PointF point = RegularFormAtX(kFormBeginX); - return point; - } - - const CFX_PointF& RegularFormEnd() const { - static const CFX_PointF point = RegularFormAtX(kFormEndX); - return point; - } - - static CFX_PointF CharLimitFormAtX(float x) { - ASSERT(x >= kFormBeginX); - ASSERT(x <= kFormEndX); - return CFX_PointF(x, kCharLimitFormY); - } - - static CFX_PointF RegularFormAtX(float x) { - ASSERT(x >= kFormBeginX); - ASSERT(x <= kFormEndX); - return CFX_PointF(x, kRegularFormY); - } - - private: - static constexpr float kFormBeginX = 102.0; - static constexpr float kFormEndX = 195.0; - static constexpr float kCharLimitFormY = 60.0; - static constexpr float kRegularFormY = 115.0; -}; - -class FPDFFormFillComboBoxFormEmbeddertest - : public FPDFFormFillInteractiveEmbeddertest { - protected: - FPDFFormFillComboBoxFormEmbeddertest() = default; - ~FPDFFormFillComboBoxFormEmbeddertest() override = default; - - const char* GetDocumentName() const override { - // PDF with form comboboxes: - // - "Combo_Editable" - Ff: 393216, 3 options with pair values. - // - "Combo1" - Ff: 131072, 3 options with single values. - // - "Combo_ReadOnly" - Ff: 131073, 3 options with single values. - return "combobox_form.pdf"; - } - - int GetFormType() const override { return FPDF_FORMFIELD_COMBOBOX; } - - void FormSanityChecks() override { - EXPECT_EQ(GetFormType(), GetFormTypeAtPoint(EditableFormBegin())); - EXPECT_EQ(GetFormType(), GetFormTypeAtPoint(EditableFormEnd())); - EXPECT_EQ(GetFormType(), GetFormTypeAtPoint(EditableFormDropDown())); - EXPECT_EQ(GetFormType(), GetFormTypeAtPoint(NonEditableFormBegin())); - EXPECT_EQ(GetFormType(), GetFormTypeAtPoint(NonEditableFormEnd())); - EXPECT_EQ(GetFormType(), GetFormTypeAtPoint(NonEditableFormDropDown())); - } - - void SelectEditableFormOption(int item_index) { - ASSERT(item_index >= 0); - ASSERT(item_index < 3); - SelectOption(item_index, EditableFormDropDown()); - } - - void SelectNonEditableFormOption(int item_index) { - ASSERT(item_index >= 0); - ASSERT(item_index < 26); - SelectOption(item_index, NonEditableFormDropDown()); - } - - void SelectAllEditableFormTextWithMouse() { - SelectTextWithMouse(EditableFormEnd(), EditableFormBegin()); - } - - const CFX_PointF& EditableFormBegin() const { - static const CFX_PointF point = EditableFormAtX(kFormBeginX); - return point; - } - - const CFX_PointF& EditableFormEnd() const { - static const CFX_PointF point = EditableFormAtX(kFormEndX); - return point; - } - - const CFX_PointF& EditableFormDropDown() const { - static const CFX_PointF point(kFormDropDownX, kEditableFormY); - return point; - } - - const CFX_PointF& NonEditableFormBegin() const { - static const CFX_PointF point = NonEditableFormAtX(kFormBeginX); - return point; - } - - const CFX_PointF& NonEditableFormEnd() const { - static const CFX_PointF point = NonEditableFormAtX(kFormEndX); - return point; - } - - const CFX_PointF& NonEditableFormDropDown() const { - static const CFX_PointF point(kFormDropDownX, kNonEditableFormY); - return point; - } - - static CFX_PointF EditableFormAtX(float x) { - ASSERT(x >= kFormBeginX); - ASSERT(x <= kFormEndX); - return CFX_PointF(x, kEditableFormY); - } - - static CFX_PointF NonEditableFormAtX(float x) { - ASSERT(x >= kFormBeginX); - ASSERT(x <= kFormEndX); - return CFX_PointF(x, kNonEditableFormY); - } - - private: - // Selects one of the pre-selected values from a combobox with three options. - // Options are specified by |item_index|, which is 0-based. - void SelectOption(int item_index, const CFX_PointF& point) { - - // Navigate to button for drop down and click mouse to reveal options. - ClickOnFormFieldAtPoint(point); - - // Calculate to Y-coordinate of dropdown option to be selected. - constexpr double kChoiceHeight = 15; - CFX_PointF option_point = point; - option_point.y -= kChoiceHeight * (item_index + 1); - - // Move left to avoid scrollbar. - option_point.x -= 20; - - // Navigate to option and click mouse to select it. - ClickOnFormFieldAtPoint(option_point); - } - - static constexpr float kFormBeginX = 102.0; - static constexpr float kFormEndX = 183.0; - static constexpr float kFormDropDownX = 192.0; - static constexpr float kEditableFormY = 360.0; - static constexpr float kNonEditableFormY = 410.0; -}; - -TEST_F(FPDFFormFillEmbeddertest, FirstTest) { - EmbedderTestMockDelegate mock; - EXPECT_CALL(mock, Alert(_, _, _, _)).Times(0); - EXPECT_CALL(mock, UnsupportedHandler(_)).Times(0); - EXPECT_CALL(mock, SetTimer(_, _)).Times(0); - EXPECT_CALL(mock, KillTimer(_)).Times(0); - SetDelegate(&mock); - - EXPECT_TRUE(OpenDocument("hello_world.pdf")); - FPDF_PAGE page = LoadPage(0); - EXPECT_TRUE(page); - UnloadPage(page); -} - -TEST_F(FPDFFormFillEmbeddertest, BUG_487928) { - EmbedderTestTimerHandlingDelegate delegate; - SetDelegate(&delegate); - - EXPECT_TRUE(OpenDocument("bug_487928.pdf")); - FPDF_PAGE page = LoadPage(0); - EXPECT_TRUE(page); - DoOpenActions(); - delegate.AdvanceTime(5000); - UnloadPage(page); -} - -TEST_F(FPDFFormFillEmbeddertest, BUG_507316) { - EmbedderTestTimerHandlingDelegate delegate; - SetDelegate(&delegate); - - EXPECT_TRUE(OpenDocument("bug_507316.pdf")); - FPDF_PAGE page = LoadPage(2); - EXPECT_TRUE(page); - DoOpenActions(); - delegate.AdvanceTime(4000); - UnloadPage(page); -} - -TEST_F(FPDFFormFillEmbeddertest, BUG_514690) { - EXPECT_TRUE(OpenDocument("hello_world.pdf")); - FPDF_PAGE page = LoadPage(0); - EXPECT_TRUE(page); - - // Test that FORM_OnMouseMove() etc. permit null HANDLES and PAGES. - FORM_OnMouseMove(nullptr, page, 0, 10.0, 10.0); - FORM_OnMouseMove(form_handle(), nullptr, 0, 10.0, 10.0); - - UnloadPage(page); -} - -#ifdef PDF_ENABLE_V8 -TEST_F(FPDFFormFillEmbeddertest, BUG_551248) { - // Test that timers fire once and intervals fire repeatedly. - EmbedderTestTimerHandlingDelegate delegate; - SetDelegate(&delegate); - - EXPECT_TRUE(OpenDocument("bug_551248.pdf")); - FPDF_PAGE page = LoadPage(0); - EXPECT_TRUE(page); - DoOpenActions(); - - const auto& alerts = delegate.GetAlerts(); - EXPECT_EQ(0U, alerts.size()); - - delegate.AdvanceTime(1000); - EXPECT_EQ(0U, alerts.size()); // nothing fired. - delegate.AdvanceTime(1000); - EXPECT_EQ(1U, alerts.size()); // interval fired. - delegate.AdvanceTime(1000); - EXPECT_EQ(2U, alerts.size()); // timer fired. - delegate.AdvanceTime(1000); - EXPECT_EQ(3U, alerts.size()); // interval fired again. - delegate.AdvanceTime(1000); - EXPECT_EQ(3U, alerts.size()); // nothing fired. - delegate.AdvanceTime(1000); - EXPECT_EQ(4U, alerts.size()); // interval fired again. - delegate.AdvanceTime(1000); - EXPECT_EQ(4U, alerts.size()); // nothing fired. - UnloadPage(page); - - ASSERT_EQ(4U, alerts.size()); // nothing else fired. - - EXPECT_STREQ(L"interval fired", alerts[0].message.c_str()); - EXPECT_STREQ(L"Alert", alerts[0].title.c_str()); - EXPECT_EQ(0, alerts[0].type); - EXPECT_EQ(0, alerts[0].icon); - - EXPECT_STREQ(L"timer fired", alerts[1].message.c_str()); - EXPECT_STREQ(L"Alert", alerts[1].title.c_str()); - EXPECT_EQ(0, alerts[1].type); - EXPECT_EQ(0, alerts[1].icon); - - EXPECT_STREQ(L"interval fired", alerts[2].message.c_str()); - EXPECT_STREQ(L"Alert", alerts[2].title.c_str()); - EXPECT_EQ(0, alerts[2].type); - EXPECT_EQ(0, alerts[2].icon); - - EXPECT_STREQ(L"interval fired", alerts[3].message.c_str()); - EXPECT_STREQ(L"Alert", alerts[3].title.c_str()); - EXPECT_EQ(0, alerts[3].type); - EXPECT_EQ(0, alerts[3].icon); -} - -TEST_F(FPDFFormFillEmbeddertest, BUG_620428) { - // Test that timers and intervals are cancelable. - EmbedderTestTimerHandlingDelegate delegate; - SetDelegate(&delegate); - - EXPECT_TRUE(OpenDocument("bug_620428.pdf")); - FPDF_PAGE page = LoadPage(0); - EXPECT_TRUE(page); - DoOpenActions(); - delegate.AdvanceTime(5000); - UnloadPage(page); - - const auto& alerts = delegate.GetAlerts(); - ASSERT_EQ(1U, alerts.size()); - EXPECT_STREQ(L"done", alerts[0].message.c_str()); -} - -TEST_F(FPDFFormFillEmbeddertest, BUG_634394) { - // Cancel timer inside timer callback. - EmbedderTestTimerHandlingDelegate delegate; - SetDelegate(&delegate); - - EXPECT_TRUE(OpenDocument("bug_634394.pdf")); - FPDF_PAGE page = LoadPage(0); - EXPECT_TRUE(page); - DoOpenActions(); - - // Timers fire at most once per AdvanceTime(), allow intervals - // to fire several times if possible. - delegate.AdvanceTime(1000); - delegate.AdvanceTime(1000); - delegate.AdvanceTime(1000); - delegate.AdvanceTime(1000); - delegate.AdvanceTime(1000); - UnloadPage(page); - - const auto& alerts = delegate.GetAlerts(); - EXPECT_EQ(2U, alerts.size()); -} - -TEST_F(FPDFFormFillEmbeddertest, BUG_634716) { - EmbedderTestTimerHandlingDelegate delegate; - SetDelegate(&delegate); - - EXPECT_TRUE(OpenDocument("bug_634716.pdf")); - FPDF_PAGE page = LoadPage(0); - EXPECT_TRUE(page); - DoOpenActions(); - - // Timers fire at most once per AdvanceTime(), allow intervals - // to fire several times if possible. - delegate.AdvanceTime(1000); - delegate.AdvanceTime(1000); - delegate.AdvanceTime(1000); - delegate.AdvanceTime(1000); - delegate.AdvanceTime(1000); - UnloadPage(page); - - const auto& alerts = delegate.GetAlerts(); - EXPECT_EQ(2U, alerts.size()); -} - -TEST_F(FPDFFormFillEmbeddertest, BUG_679649) { - EmbedderTestTimerHandlingDelegate delegate; - SetDelegate(&delegate); - - EXPECT_TRUE(OpenDocument("bug_679649.pdf")); - FPDF_PAGE page = LoadPage(0); - EXPECT_TRUE(page); - - delegate.SetFailNextTimer(); - DoOpenActions(); - delegate.AdvanceTime(2000); - UnloadPage(page); - - const auto& alerts = delegate.GetAlerts(); - EXPECT_EQ(0u, alerts.size()); -} - -TEST_F(FPDFFormFillEmbeddertest, BUG_707673) { - EmbedderTestTimerHandlingDelegate delegate; - SetDelegate(&delegate); - - EXPECT_TRUE(OpenDocument("bug_707673.pdf")); - FPDF_PAGE page = LoadPage(0); - EXPECT_TRUE(page); - - DoOpenActions(); - FORM_OnLButtonDown(form_handle(), page, 0, 140, 590); - FORM_OnLButtonUp(form_handle(), page, 0, 140, 590); - delegate.AdvanceTime(1000); - UnloadPage(page); - - const auto& alerts = delegate.GetAlerts(); - EXPECT_EQ(0u, alerts.size()); -} - -TEST_F(FPDFFormFillEmbeddertest, BUG_765384) { - EXPECT_TRUE(OpenDocument("bug_765384.pdf")); - FPDF_PAGE page = LoadPage(0); - EXPECT_TRUE(page); - - DoOpenActions(); - FORM_OnLButtonDown(form_handle(), page, 0, 140, 590); - FORM_OnLButtonUp(form_handle(), page, 0, 140, 590); - UnloadPage(page); -} - -#endif // PDF_ENABLE_V8 - -TEST_F(FPDFFormFillEmbeddertest, FormText) { -#if _FX_PLATFORM_ == _FX_PLATFORM_APPLE_ - const char md5_1[] = "5f11dbe575fe197a37c3fb422559f8ff"; - const char md5_2[] = "35b1a4b679eafc749a0b6fda750c0e8d"; - const char md5_3[] = "65c64a7c355388f719a752aa1e23f6fe"; -#else - const char md5_1[] = "b890950d4b9bc163b1a96797f3004b53"; - const char md5_2[] = "11487d5597599a26e8912b9c1d9422cb"; - const char md5_3[] = "bffe0ecea9a533f217047ee41d6be466"; -#endif - { - EXPECT_TRUE(OpenDocument("text_form.pdf")); - FPDF_PAGE page = LoadPage(0); - ASSERT_TRUE(page); - std::unique_ptr bitmap1 = RenderLoadedPage(page); - CompareBitmap(bitmap1.get(), 300, 300, md5_1); - - // Click on the textfield - EXPECT_EQ(FPDF_FORMFIELD_TEXTFIELD, - FPDFPage_HasFormFieldAtPoint(form_handle(), page, 120.0, 120.0)); - FORM_OnMouseMove(form_handle(), page, 0, 120.0, 120.0); - FORM_OnLButtonDown(form_handle(), page, 0, 120.0, 120.0); - FORM_OnLButtonUp(form_handle(), page, 0, 120.0, 120.0); - - // Write "ABC" - FORM_OnChar(form_handle(), page, 65, 0); - FORM_OnChar(form_handle(), page, 66, 0); - FORM_OnChar(form_handle(), page, 67, 0); - std::unique_ptr bitmap2 = RenderLoadedPage(page); - CompareBitmap(bitmap2.get(), 300, 300, md5_2); - - // Take out focus by clicking out of the textfield - FORM_OnMouseMove(form_handle(), page, 0, 15.0, 15.0); - FORM_OnLButtonDown(form_handle(), page, 0, 15.0, 15.0); - FORM_OnLButtonUp(form_handle(), page, 0, 15.0, 15.0); - std::unique_ptr bitmap3 = RenderLoadedPage(page); - CompareBitmap(bitmap3.get(), 300, 300, md5_3); - - EXPECT_TRUE(FPDF_SaveAsCopy(document(), this, 0)); - - // Close page - UnloadPage(page); - } - // Check saved document - VerifySavedDocument(300, 300, md5_3); -} - -TEST_F(FPDFFormFillEmbeddertest, HasFormInfoNone) { - EXPECT_TRUE(OpenDocument("hello_world.pdf")); - EXPECT_EQ(FORMTYPE_NONE, FPDF_GetFormType(document_)); -} - -TEST_F(FPDFFormFillEmbeddertest, HasFormInfoAcroForm) { - EXPECT_TRUE(OpenDocument("text_form.pdf")); - EXPECT_EQ(FORMTYPE_ACRO_FORM, FPDF_GetFormType(document_)); -} - -TEST_F(FPDFFormFillEmbeddertest, HasFormInfoXFAFull) { - EXPECT_TRUE(OpenDocument("simple_xfa.pdf")); - EXPECT_EQ(FORMTYPE_XFA_FULL, FPDF_GetFormType(document_)); -} - -TEST_F(FPDFFormFillEmbeddertest, HasFormInfoXFAForeground) { - EXPECT_TRUE(OpenDocument("bug_216.pdf")); - EXPECT_EQ(FORMTYPE_XFA_FOREGROUND, FPDF_GetFormType(document_)); -} - -TEST_F(FPDFFormFillTextFormEmbeddertest, GetSelectedTextEmptyAndBasicKeyboard) { - // Test empty selection. - CheckSelection(L""); - - // Test basic selection. - TypeTextIntoTextField(3, RegularFormBegin()); - SelectTextWithKeyboard(3, FWL_VKEY_Left, RegularFormAtX(123.0)); - CheckSelection(L"ABC"); -} - -TEST_F(FPDFFormFillTextFormEmbeddertest, GetSelectedTextEmptyAndBasicMouse) { - // Test empty selection. - CheckSelection(L""); - - // Test basic selection. - TypeTextIntoTextField(3, RegularFormBegin()); - SelectTextWithMouse(RegularFormAtX(125.0), RegularFormBegin()); - CheckSelection(L"ABC"); -} - -TEST_F(FPDFFormFillTextFormEmbeddertest, GetSelectedTextFragmentsKeyBoard) { - TypeTextIntoTextField(12, RegularFormBegin()); - - // Test selecting first character in forward direction. - SelectTextWithKeyboard(1, FWL_VKEY_Right, RegularFormBegin()); - CheckSelection(L"A"); - - // Test selecting entire long string in backwards direction. - SelectTextWithKeyboard(12, FWL_VKEY_Left, RegularFormEnd()); - CheckSelection(L"ABCDEFGHIJKL"); - - // Test selecting middle section in backwards direction. - SelectTextWithKeyboard(6, FWL_VKEY_Left, RegularFormAtX(170.0)); - CheckSelection(L"DEFGHI"); - - // Test selecting middle selection in forward direction. - SelectTextWithKeyboard(6, FWL_VKEY_Right, RegularFormAtX(125.0)); - CheckSelection(L"DEFGHI"); - - // Test selecting last character in backwards direction. - SelectTextWithKeyboard(1, FWL_VKEY_Left, RegularFormEnd()); - CheckSelection(L"L"); -} - -TEST_F(FPDFFormFillTextFormEmbeddertest, GetSelectedTextFragmentsMouse) { - TypeTextIntoTextField(12, RegularFormBegin()); - - // Test selecting first character in forward direction. - SelectTextWithMouse(RegularFormBegin(), RegularFormAtX(106.0)); - CheckSelection(L"A"); - - // Test selecting entire long string in backwards direction. - SelectAllRegularFormTextWithMouse(); - CheckSelection(L"ABCDEFGHIJKL"); - - // Test selecting middle section in backwards direction. - SelectTextWithMouse(RegularFormAtX(170.0), RegularFormAtX(125.0)); - CheckSelection(L"DEFGHI"); - - // Test selecting middle selection in forward direction. - SelectTextWithMouse(RegularFormAtX(125.0), RegularFormAtX(170.0)); - CheckSelection(L"DEFGHI"); - - // Test selecting last character in backwards direction. - SelectTextWithMouse(RegularFormEnd(), RegularFormAtX(186.0)); - CheckSelection(L"L"); -} - -TEST_F(FPDFFormFillComboBoxFormEmbeddertest, - GetSelectedTextEmptyAndBasicNormalComboBox) { - // Test empty selection. - CheckSelection(L""); - - // Non-editable comboboxes don't allow selection with keyboard. - SelectTextWithMouse(NonEditableFormBegin(), NonEditableFormAtX(142.0)); - CheckSelection(L"Banana"); - - // Select other another provided option. - SelectNonEditableFormOption(0); - CheckSelection(L"Apple"); -} - -TEST_F(FPDFFormFillComboBoxFormEmbeddertest, - GetSelectedTextEmptyAndBasicEditableComboBoxKeyboard) { - // Test empty selection. - CheckSelection(L""); - - // Test basic selection of text within user editable combobox using keyboard. - TypeTextIntoTextField(3, EditableFormBegin()); - SelectTextWithKeyboard(3, FWL_VKEY_Left, EditableFormAtX(128.0)); - CheckSelection(L"ABC"); - - // Select a provided option. - SelectEditableFormOption(1); - CheckSelection(L"Bar"); -} - -TEST_F(FPDFFormFillComboBoxFormEmbeddertest, - GetSelectedTextEmptyAndBasicEditableComboBoxMouse) { - // Test empty selection. - CheckSelection(L""); - - // Test basic selection of text within user editable combobox using mouse. - TypeTextIntoTextField(3, EditableFormBegin()); - SelectTextWithMouse(EditableFormAtX(128.0), EditableFormBegin()); - CheckSelection(L"ABC"); - - // Select a provided option. - SelectEditableFormOption(2); - CheckSelection(L"Qux"); -} - -TEST_F(FPDFFormFillComboBoxFormEmbeddertest, - GetSelectedTextFragmentsNormalComboBox) { - // Test selecting first character in forward direction. - SelectTextWithMouse(NonEditableFormBegin(), NonEditableFormAtX(107.0)); - CheckSelection(L"B"); - - // Test selecting entire string in backwards direction. - SelectTextWithMouse(NonEditableFormAtX(142.0), NonEditableFormBegin()); - CheckSelection(L"Banana"); - - // Test selecting middle section in backwards direction. - SelectTextWithMouse(NonEditableFormAtX(135.0), NonEditableFormAtX(117.0)); - CheckSelection(L"nan"); - - // Test selecting middle section in forward direction. - SelectTextWithMouse(NonEditableFormAtX(117.0), NonEditableFormAtX(135.0)); - CheckSelection(L"nan"); - - // Test selecting last character in backwards direction. - SelectTextWithMouse(NonEditableFormAtX(142.0), NonEditableFormAtX(138.0)); - CheckSelection(L"a"); - - // Select another option and then reset selection as first three chars. - SelectNonEditableFormOption(2); - CheckSelection(L"Cherry"); - SelectTextWithMouse(NonEditableFormBegin(), NonEditableFormAtX(122.0)); - CheckSelection(L"Che"); -} - -TEST_F(FPDFFormFillComboBoxFormEmbeddertest, - GetSelectedTextFragmentsEditableComboBoxKeyboard) { - TypeTextIntoTextField(10, EditableFormBegin()); - - // Test selecting first character in forward direction. - SelectTextWithKeyboard(1, FWL_VKEY_Right, EditableFormBegin()); - CheckSelection(L"A"); - - // Test selecting entire long string in backwards direction. - SelectTextWithKeyboard(10, FWL_VKEY_Left, EditableFormEnd()); - CheckSelection(L"ABCDEFGHIJ"); - - // Test selecting middle section in backwards direction. - SelectTextWithKeyboard(5, FWL_VKEY_Left, EditableFormAtX(168.0)); - CheckSelection(L"DEFGH"); - - // Test selecting middle selection in forward direction. - SelectTextWithKeyboard(5, FWL_VKEY_Right, EditableFormAtX(127.0)); - CheckSelection(L"DEFGH"); - - // Test selecting last character in backwards direction. - SelectTextWithKeyboard(1, FWL_VKEY_Left, EditableFormEnd()); - CheckSelection(L"J"); - - // Select a provided option and then reset selection as first two chars. - SelectEditableFormOption(0); - CheckSelection(L"Foo"); - SelectTextWithKeyboard(2, FWL_VKEY_Right, EditableFormBegin()); - CheckSelection(L"Fo"); -} - -TEST_F(FPDFFormFillComboBoxFormEmbeddertest, - GetSelectedTextFragmentsEditableComboBoxMouse) { - TypeTextIntoTextField(10, EditableFormBegin()); - - // Test selecting first character in forward direction. - SelectTextWithMouse(EditableFormBegin(), EditableFormAtX(107.0)); - CheckSelection(L"A"); - - // Test selecting entire long string in backwards direction. - SelectAllEditableFormTextWithMouse(); - CheckSelection(L"ABCDEFGHIJ"); - - // Test selecting middle section in backwards direction. - SelectTextWithMouse(EditableFormAtX(168.0), EditableFormAtX(127.0)); - CheckSelection(L"DEFGH"); - - // Test selecting middle selection in forward direction. - SelectTextWithMouse(EditableFormAtX(127.0), EditableFormAtX(168.0)); - CheckSelection(L"DEFGH"); - - // Test selecting last character in backwards direction. - SelectTextWithMouse(EditableFormEnd(), EditableFormAtX(174.0)); - CheckSelection(L"J"); -} - -TEST_F(FPDFFormFillTextFormEmbeddertest, DeleteTextFieldEntireSelection) { - // Select entire contents of text field. - TypeTextIntoTextField(12, RegularFormBegin()); - SelectAllRegularFormTextWithMouse(); - CheckSelection(L"ABCDEFGHIJKL"); - - // Test deleting current text selection. Select what remains after deletion to - // check that remaining text is as expected. - FORM_ReplaceSelection(form_handle(), page(), nullptr); - - SelectTextWithKeyboard(12, FWL_VKEY_Left, RegularFormEnd()); - CheckSelection(L""); -} - -TEST_F(FPDFFormFillTextFormEmbeddertest, DeleteTextFieldSelectionMiddle) { - // Select middle section of text. - TypeTextIntoTextField(12, RegularFormBegin()); - SelectTextWithMouse(RegularFormAtX(170.0), RegularFormAtX(125.0)); - CheckSelection(L"DEFGHI"); - - // Test deleting current text selection. Select what remains after deletion to - // check that remaining text is as expected. - FORM_ReplaceSelection(form_handle(), page(), nullptr); - SelectTextWithKeyboard(12, FWL_VKEY_Left, RegularFormEnd()); - CheckSelection(L"ABCJKL"); -} - -TEST_F(FPDFFormFillTextFormEmbeddertest, DeleteTextFieldSelectionLeft) { - // Select first few characters of text. - TypeTextIntoTextField(12, RegularFormBegin()); - SelectTextWithMouse(RegularFormBegin(), RegularFormAtX(132.0)); - CheckSelection(L"ABCD"); - - // Test deleting current text selection. Select what remains after deletion to - // check that remaining text is as expected. - FORM_ReplaceSelection(form_handle(), page(), nullptr); - SelectTextWithKeyboard(12, FWL_VKEY_Left, RegularFormEnd()); - CheckSelection(L"EFGHIJKL"); -} - -TEST_F(FPDFFormFillTextFormEmbeddertest, DeleteTextFieldSelectionRight) { - // Select last few characters of text. - TypeTextIntoTextField(12, RegularFormBegin()); - SelectTextWithMouse(RegularFormEnd(), RegularFormAtX(165.0)); - CheckSelection(L"IJKL"); - - // Test deleting current text selection. Select what remains after deletion to - // check that remaining text is as expected. - FORM_ReplaceSelection(form_handle(), page(), nullptr); - SelectTextWithKeyboard(12, FWL_VKEY_Left, RegularFormEnd()); - CheckSelection(L"ABCDEFGH"); -} - -TEST_F(FPDFFormFillTextFormEmbeddertest, DeleteEmptyTextFieldSelection) { - // Do not select text. - TypeTextIntoTextField(12, RegularFormBegin()); - CheckSelection(L""); - - // Test that attempt to delete empty text selection has no effect. - FORM_ReplaceSelection(form_handle(), page(), nullptr); - SelectTextWithKeyboard(12, FWL_VKEY_Left, RegularFormEnd()); - CheckSelection(L"ABCDEFGHIJKL"); -} - -TEST_F(FPDFFormFillComboBoxFormEmbeddertest, - DeleteEditableComboBoxEntireSelection) { - // Select entire contents of user-editable combobox text field. - TypeTextIntoTextField(10, EditableFormBegin()); - SelectAllEditableFormTextWithMouse(); - CheckSelection(L"ABCDEFGHIJ"); - - // Test deleting current text selection. Select what remains after deletion to - // check that remaining text is as expected. - FORM_ReplaceSelection(form_handle(), page(), nullptr); - SelectAllEditableFormTextWithMouse(); - CheckSelection(L""); -} - -TEST_F(FPDFFormFillComboBoxFormEmbeddertest, - DeleteEditableComboBoxSelectionMiddle) { - // Select middle section of text. - TypeTextIntoTextField(10, EditableFormBegin()); - SelectTextWithMouse(EditableFormAtX(168.0), EditableFormAtX(127.0)); - CheckSelection(L"DEFGH"); - - // Test deleting current text selection. Select what remains after deletion to - // check that remaining text is as expected. - FORM_ReplaceSelection(form_handle(), page(), nullptr); - SelectAllEditableFormTextWithMouse(); - CheckSelection(L"ABCIJ"); -} - -TEST_F(FPDFFormFillComboBoxFormEmbeddertest, - DeleteEditableComboBoxSelectionLeft) { - // Select first few characters of text. - TypeTextIntoTextField(10, EditableFormBegin()); - SelectTextWithMouse(EditableFormBegin(), EditableFormAtX(132.0)); - CheckSelection(L"ABCD"); - - // Test deleting current text selection. Select what remains after deletion to - // check that remaining text is as expected. - FORM_ReplaceSelection(form_handle(), page(), nullptr); - SelectAllEditableFormTextWithMouse(); - CheckSelection(L"EFGHIJ"); -} - -TEST_F(FPDFFormFillComboBoxFormEmbeddertest, - DeleteEditableComboBoxSelectionRight) { - // Select last few characters of text. - TypeTextIntoTextField(10, EditableFormBegin()); - SelectTextWithMouse(EditableFormEnd(), EditableFormAtX(152.0)); - CheckSelection(L"GHIJ"); - - // Test deleting current text selection. Select what remains after deletion to - // check that remaining text is as expected. - FORM_ReplaceSelection(form_handle(), page(), nullptr); - SelectAllEditableFormTextWithMouse(); - CheckSelection(L"ABCDEF"); -} - -TEST_F(FPDFFormFillComboBoxFormEmbeddertest, - DeleteEmptyEditableComboBoxSelection) { - // Do not select text. - TypeTextIntoTextField(10, EditableFormBegin()); - CheckSelection(L""); - - // Test that attempt to delete empty text selection has no effect. - FORM_ReplaceSelection(form_handle(), page(), nullptr); - SelectAllEditableFormTextWithMouse(); - CheckSelection(L"ABCDEFGHIJ"); -} - -TEST_F(FPDFFormFillTextFormEmbeddertest, InsertTextInEmptyTextField) { - ClickOnFormFieldAtPoint(RegularFormBegin()); - - // Test inserting text into empty text field. - std::unique_ptr text_to_insert = - GetFPDFWideString(L"Hello"); - FORM_ReplaceSelection(form_handle(), page(), text_to_insert.get()); - - // Select entire contents of text field to check that insertion worked - // as expected. - SelectAllRegularFormTextWithMouse(); - CheckSelection(L"Hello"); -} - -TEST_F(FPDFFormFillTextFormEmbeddertest, InsertTextInPopulatedTextFieldLeft) { - TypeTextIntoTextField(8, RegularFormBegin()); - - // Click on the leftmost part of the text field. - ClickOnFormFieldAtPoint(RegularFormBegin()); - - // Test inserting text in front of existing text in text field. - std::unique_ptr text_to_insert = - GetFPDFWideString(L"Hello"); - FORM_ReplaceSelection(form_handle(), page(), text_to_insert.get()); - - // Select entire contents of text field to check that insertion worked - // as expected. - SelectAllRegularFormTextWithMouse(); - CheckSelection(L"HelloABCDEFGH"); -} - -TEST_F(FPDFFormFillTextFormEmbeddertest, InsertTextInPopulatedTextFieldMiddle) { - TypeTextIntoTextField(8, RegularFormBegin()); - - // Click on the middle of the text field. - ClickOnFormFieldAtPoint(RegularFormAtX(134.0)); - - // Test inserting text in the middle of existing text in text field. - std::unique_ptr text_to_insert = - GetFPDFWideString(L"Hello"); - FORM_ReplaceSelection(form_handle(), page(), text_to_insert.get()); - - // Select entire contents of text field to check that insertion worked - // as expected. - SelectAllRegularFormTextWithMouse(); - CheckSelection(L"ABCDHelloEFGH"); -} - -TEST_F(FPDFFormFillTextFormEmbeddertest, InsertTextInPopulatedTextFieldRight) { - TypeTextIntoTextField(8, RegularFormBegin()); - - // Click on the rightmost part of the text field. - ClickOnFormFieldAtPoint(RegularFormAtX(166.0)); - - // Test inserting text behind existing text in text field. - std::unique_ptr text_to_insert = - GetFPDFWideString(L"Hello"); - FORM_ReplaceSelection(form_handle(), page(), text_to_insert.get()); - - // Select entire contents of text field to check that insertion worked - // as expected. - SelectAllRegularFormTextWithMouse(); - CheckSelection(L"ABCDEFGHHello"); -} - -TEST_F(FPDFFormFillTextFormEmbeddertest, - InsertTextAndReplaceSelectionInPopulatedTextFieldWhole) { - TypeTextIntoTextField(12, RegularFormBegin()); - - // Select entire string in text field. - SelectTextWithKeyboard(12, FWL_VKEY_Left, RegularFormEnd()); - CheckSelection(L"ABCDEFGHIJKL"); - - // Test replacing text selection with text to be inserted. - std::unique_ptr text_to_insert = - GetFPDFWideString(L"Hello"); - FORM_ReplaceSelection(form_handle(), page(), text_to_insert.get()); - - // Select entire contents of text field to check that insertion worked - // as expected. - SelectAllRegularFormTextWithMouse(); - CheckSelection(L"Hello"); -} - -TEST_F(FPDFFormFillTextFormEmbeddertest, - InsertTextAndReplaceSelectionInPopulatedTextFieldLeft) { - TypeTextIntoTextField(12, RegularFormBegin()); - - // Select left portion of string in text field. - SelectTextWithKeyboard(6, FWL_VKEY_Left, RegularFormAtX(148.0)); - CheckSelection(L"ABCDEF"); - - // Test replacing text selection with text to be inserted. - std::unique_ptr text_to_insert = - GetFPDFWideString(L"Hello"); - FORM_ReplaceSelection(form_handle(), page(), text_to_insert.get()); - - // Select entire contents of text field to check that insertion worked - // as expected. - SelectAllRegularFormTextWithMouse(); - CheckSelection(L"HelloGHIJKL"); -} - -TEST_F(FPDFFormFillTextFormEmbeddertest, - InsertTextAndReplaceSelectionInPopulatedTextFieldMiddle) { - TypeTextIntoTextField(12, RegularFormBegin()); - - // Select middle portion of string in text field. - SelectTextWithKeyboard(6, FWL_VKEY_Left, RegularFormAtX(171.0)); - CheckSelection(L"DEFGHI"); - - // Test replacing text selection with text to be inserted. - std::unique_ptr text_to_insert = - GetFPDFWideString(L"Hello"); - FORM_ReplaceSelection(form_handle(), page(), text_to_insert.get()); - - // Select entire contents of text field to check that insertion worked - // as expected. - SelectAllRegularFormTextWithMouse(); - CheckSelection(L"ABCHelloJKL"); -} - -TEST_F(FPDFFormFillTextFormEmbeddertest, - InsertTextAndReplaceSelectionInPopulatedTextFieldRight) { - TypeTextIntoTextField(12, RegularFormBegin()); - - // Select right portion of string in text field. - SelectTextWithKeyboard(6, FWL_VKEY_Left, RegularFormEnd()); - CheckSelection(L"GHIJKL"); - - // Test replacing text selection with text to be inserted. - std::unique_ptr text_to_insert = - GetFPDFWideString(L"Hello"); - FORM_ReplaceSelection(form_handle(), page(), text_to_insert.get()); - - // Select entire contents of text field to check that insertion worked - // as expected. - SelectAllRegularFormTextWithMouse(); - CheckSelection(L"ABCDEFHello"); -} - -TEST_F(FPDFFormFillComboBoxFormEmbeddertest, - InsertTextInEmptyEditableComboBox) { - ClickOnFormFieldAtPoint(EditableFormBegin()); - - // Test inserting text into empty user-editable combobox. - std::unique_ptr text_to_insert = - GetFPDFWideString(L"Hello"); - FORM_ReplaceSelection(form_handle(), page(), text_to_insert.get()); - - // Select entire contents of user-editable combobox text field to check that - // insertion worked as expected. - SelectAllEditableFormTextWithMouse(); - CheckSelection(L"Hello"); -} - -TEST_F(FPDFFormFillComboBoxFormEmbeddertest, - InsertTextInPopulatedEditableComboBoxLeft) { - TypeTextIntoTextField(6, EditableFormBegin()); - - // Click on the leftmost part of the user-editable combobox. - ClickOnFormFieldAtPoint(EditableFormBegin()); - - // Test inserting text in front of existing text in user-editable combobox. - std::unique_ptr text_to_insert = - GetFPDFWideString(L"Hello"); - FORM_ReplaceSelection(form_handle(), page(), text_to_insert.get()); - - // Select entire contents of user-editable combobox text field to check that - // insertion worked as expected. - SelectAllEditableFormTextWithMouse(); - CheckSelection(L"HelloABCDEF"); -} - -TEST_F(FPDFFormFillComboBoxFormEmbeddertest, - InsertTextInPopulatedEditableComboBoxMiddle) { - TypeTextIntoTextField(6, EditableFormBegin()); - - // Click on the middle of the user-editable combobox. - ClickOnFormFieldAtPoint(EditableFormAtX(126.0)); - - // Test inserting text in the middle of existing text in user-editable - // combobox. - std::unique_ptr text_to_insert = - GetFPDFWideString(L"Hello"); - FORM_ReplaceSelection(form_handle(), page(), text_to_insert.get()); - - // Select entire contents of user-editable combobox text field to check that - // insertion worked as expected. - SelectAllEditableFormTextWithMouse(); - CheckSelection(L"ABCHelloDEF"); -} - -TEST_F(FPDFFormFillComboBoxFormEmbeddertest, - InsertTextInPopulatedEditableComboBoxRight) { - TypeTextIntoTextField(6, EditableFormBegin()); - - // Click on the rightmost part of the user-editable combobox. - ClickOnFormFieldAtPoint(EditableFormEnd()); - - // Test inserting text behind existing text in user-editable combobox. - std::unique_ptr text_to_insert = - GetFPDFWideString(L"Hello"); - FORM_ReplaceSelection(form_handle(), page(), text_to_insert.get()); - - // Select entire contents of user-editable combobox text field to check that - // insertion worked as expected. - SelectAllEditableFormTextWithMouse(); - CheckSelection(L"ABCDEFHello"); -} - -TEST_F(FPDFFormFillComboBoxFormEmbeddertest, - InsertTextAndReplaceSelectionInPopulatedEditableComboBoxWhole) { - TypeTextIntoTextField(10, EditableFormBegin()); - - // Select entire string in user-editable combobox. - SelectTextWithKeyboard(10, FWL_VKEY_Left, EditableFormEnd()); - CheckSelection(L"ABCDEFGHIJ"); - - // Test replacing text selection with text to be inserted. - std::unique_ptr text_to_insert = - GetFPDFWideString(L"Hello"); - FORM_ReplaceSelection(form_handle(), page(), text_to_insert.get()); - - // Select entire contents of user-editable combobox text field to check that - // insertion worked as expected. - SelectAllEditableFormTextWithMouse(); - CheckSelection(L"Hello"); -} - -TEST_F(FPDFFormFillComboBoxFormEmbeddertest, - InsertTextAndReplaceSelectionInPopulatedEditableComboBoxLeft) { - TypeTextIntoTextField(10, EditableFormBegin()); - - // Select left portion of string in user-editable combobox. - SelectTextWithKeyboard(5, FWL_VKEY_Left, EditableFormAtX(142.0)); - CheckSelection(L"ABCDE"); - - // Test replacing text selection with text to be inserted. - std::unique_ptr text_to_insert = - GetFPDFWideString(L"Hello"); - FORM_ReplaceSelection(form_handle(), page(), text_to_insert.get()); - - // Select entire contents of user-editable combobox text field to check that - // insertion worked as expected. - SelectAllEditableFormTextWithMouse(); - CheckSelection(L"HelloFGHIJ"); -} - -TEST_F(FPDFFormFillComboBoxFormEmbeddertest, - InsertTextAndReplaceSelectionInPopulatedEditableComboBoxMiddle) { - TypeTextIntoTextField(10, EditableFormBegin()); - - // Select middle portion of string in user-editable combobox. - SelectTextWithKeyboard(5, FWL_VKEY_Left, EditableFormAtX(167.0)); - CheckSelection(L"DEFGH"); - - // Test replacing text selection with text to be inserted. - std::unique_ptr text_to_insert = - GetFPDFWideString(L"Hello"); - FORM_ReplaceSelection(form_handle(), page(), text_to_insert.get()); - - // Select entire contents of user-editable combobox text field to check that - // insertion worked as expected. - SelectAllEditableFormTextWithMouse(); - CheckSelection(L"ABCHelloIJ"); -} - -TEST_F(FPDFFormFillComboBoxFormEmbeddertest, - InsertTextAndReplaceSelectionInPopulatedEditableComboBoxRight) { - TypeTextIntoTextField(10, EditableFormBegin()); - - // Select right portion of string in user-editable combobox. - SelectTextWithKeyboard(5, FWL_VKEY_Left, EditableFormEnd()); - CheckSelection(L"FGHIJ"); - - // Test replacing text selection with text to be inserted. - std::unique_ptr text_to_insert = - GetFPDFWideString(L"Hello"); - FORM_ReplaceSelection(form_handle(), page(), text_to_insert.get()); - - // Select entire contents of user-editable combobox text field to check that - // insertion worked as expected. - SelectAllEditableFormTextWithMouse(); - CheckSelection(L"ABCDEHello"); -} - -TEST_F(FPDFFormFillTextFormEmbeddertest, - InsertTextInEmptyCharLimitTextFieldOverflow) { - // Click on the textfield. - ClickOnFormFieldAtPoint(CharLimitFormEnd()); - - // Delete pre-filled contents of text field with char limit. - SelectAllCharLimitFormTextWithMouse(); - CheckSelection(L"Elephant"); - FORM_ReplaceSelection(form_handle(), page(), nullptr); - - // Test inserting text into now empty text field so text to be inserted - // exceeds the char limit and is cut off. - std::unique_ptr text_to_insert = - GetFPDFWideString(L"Hippopotamus"); - FORM_ReplaceSelection(form_handle(), page(), text_to_insert.get()); - - // Select entire contents of text field to check that insertion worked - // as expected. - SelectAllCharLimitFormTextWithMouse(); - CheckSelection(L"Hippopotam"); -} - -TEST_F(FPDFFormFillTextFormEmbeddertest, - InsertTextInEmptyCharLimitTextFieldFit) { - // Click on the textfield. - ClickOnFormFieldAtPoint(CharLimitFormEnd()); - - // Delete pre-filled contents of text field with char limit. - SelectAllCharLimitFormTextWithMouse(); - CheckSelection(L"Elephant"); - FORM_ReplaceSelection(form_handle(), page(), nullptr); - - // Test inserting text into now empty text field so text to be inserted - // exceeds the char limit and is cut off. - std::unique_ptr text_to_insert = - GetFPDFWideString(L"Zebra"); - FORM_ReplaceSelection(form_handle(), page(), text_to_insert.get()); - - // Select entire contents of text field to check that insertion worked - // as expected. - SelectAllCharLimitFormTextWithMouse(); - CheckSelection(L"Zebra"); -} - -TEST_F(FPDFFormFillTextFormEmbeddertest, - InsertTextInPopulatedCharLimitTextFieldLeft) { - // Click on the leftmost part of the text field. - ClickOnFormFieldAtPoint(CharLimitFormBegin()); - - // Test inserting text in front of existing text in text field. - std::unique_ptr text_to_insert = - GetFPDFWideString(L"Hippopotamus"); - FORM_ReplaceSelection(form_handle(), page(), text_to_insert.get()); - - // Select entire contents of text field to check that insertion worked - // as expected. - SelectAllCharLimitFormTextWithMouse(); - CheckSelection(L"HiElephant"); -} - -TEST_F(FPDFFormFillTextFormEmbeddertest, - InsertTextInPopulatedCharLimitTextFieldMiddle) { - TypeTextIntoTextField(8, RegularFormBegin()); - - // Click on the middle of the text field. - ClickOnFormFieldAtPoint(CharLimitFormAtX(134.0)); - - // Test inserting text in the middle of existing text in text field. - std::unique_ptr text_to_insert = - GetFPDFWideString(L"Hippopotamus"); - FORM_ReplaceSelection(form_handle(), page(), text_to_insert.get()); - - // Select entire contents of text field to check that insertion worked - // as expected. - SelectAllCharLimitFormTextWithMouse(); - CheckSelection(L"ElephHiant"); -} - -TEST_F(FPDFFormFillTextFormEmbeddertest, - InsertTextInPopulatedCharLimitTextFieldRight) { - TypeTextIntoTextField(8, RegularFormBegin()); - - // Click on the rightmost part of the text field. - ClickOnFormFieldAtPoint(CharLimitFormAtX(166.0)); - - // Test inserting text behind existing text in text field. - std::unique_ptr text_to_insert = - GetFPDFWideString(L"Hippopotamus"); - FORM_ReplaceSelection(form_handle(), page(), text_to_insert.get()); - - // Select entire contents of text field to check that insertion worked - // as expected. - SelectAllCharLimitFormTextWithMouse(); - CheckSelection(L"ElephantHi"); -} - -TEST_F(FPDFFormFillTextFormEmbeddertest, - InsertTextAndReplaceSelectionInPopulatedCharLimitTextFieldWhole) { - TypeTextIntoTextField(12, RegularFormBegin()); - - // Select entire string in text field. - SelectTextWithKeyboard(12, FWL_VKEY_Left, CharLimitFormEnd()); - CheckSelection(L"Elephant"); - - // Test replacing text selection with text to be inserted. - std::unique_ptr text_to_insert = - GetFPDFWideString(L"Hippopotamus"); - FORM_ReplaceSelection(form_handle(), page(), text_to_insert.get()); - - // Select entire contents of text field to check that insertion worked - // as expected. - SelectAllCharLimitFormTextWithMouse(); - CheckSelection(L"Hippopotam"); -} - -TEST_F(FPDFFormFillTextFormEmbeddertest, - InsertTextAndReplaceSelectionInPopulatedCharLimitTextFieldLeft) { - TypeTextIntoTextField(12, RegularFormBegin()); - - // Select left portion of string in text field. - SelectTextWithKeyboard(4, FWL_VKEY_Left, CharLimitFormAtX(122.0)); - CheckSelection(L"Elep"); - - // Test replacing text selection with text to be inserted. - std::unique_ptr text_to_insert = - GetFPDFWideString(L"Hippopotamus"); - FORM_ReplaceSelection(form_handle(), page(), text_to_insert.get()); - - // Select entire contents of text field to check that insertion worked - // as expected. - SelectAllCharLimitFormTextWithMouse(); - CheckSelection(L"Hippophant"); -} - -TEST_F(FPDFFormFillTextFormEmbeddertest, - InsertTextAndReplaceSelectionInPopulatedCharLimitTextFieldMiddle) { - TypeTextIntoTextField(12, RegularFormBegin()); - - // Select middle portion of string in text field. - SelectTextWithKeyboard(4, FWL_VKEY_Left, CharLimitFormAtX(136.0)); - CheckSelection(L"epha"); - - // Test replacing text selection with text to be inserted. - std::unique_ptr text_to_insert = - GetFPDFWideString(L"Hippopotamus"); - FORM_ReplaceSelection(form_handle(), page(), text_to_insert.get()); - - // Select entire contents of text field to check that insertion worked - // as expected. - SelectAllCharLimitFormTextWithMouse(); - CheckSelection(L"ElHippopnt"); -} - -TEST_F(FPDFFormFillTextFormEmbeddertest, - InsertTextAndReplaceSelectionInPopulatedCharLimitTextFieldRight) { - TypeTextIntoTextField(12, RegularFormBegin()); - - // Select right portion of string in text field. - SelectTextWithKeyboard(4, FWL_VKEY_Left, CharLimitFormAtX(152.0)); - CheckSelection(L"hant"); - - // Test replacing text selection with text to be inserted. - std::unique_ptr text_to_insert = - GetFPDFWideString(L"Hippopotamus"); - FORM_ReplaceSelection(form_handle(), page(), text_to_insert.get()); - - // Select entire contents of text field to check that insertion worked - // as expected. - SelectAllCharLimitFormTextWithMouse(); - CheckSelection(L"ElepHippop"); -} diff --git a/fpdfsdk/fpdfppo_embeddertest.cpp b/fpdfsdk/fpdfppo_embeddertest.cpp deleted file mode 100644 index f62ca52048..0000000000 --- a/fpdfsdk/fpdfppo_embeddertest.cpp +++ /dev/null @@ -1,278 +0,0 @@ -// Copyright 2016 PDFium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#include -#include - -#include "public/cpp/fpdf_deleters.h" -#include "public/fpdf_edit.h" -#include "public/fpdf_ppo.h" -#include "public/fpdf_save.h" -#include "public/fpdfview.h" -#include "testing/embedder_test.h" -#include "testing/gtest/include/gtest/gtest.h" -#include "testing/test_support.h" - -namespace { - -class FPDFPPOEmbeddertest : public EmbedderTest {}; - -int FakeBlockWriter(FPDF_FILEWRITE* pThis, - const void* pData, - unsigned long size) { - return size; -} - -} // namespace - -TEST_F(FPDFPPOEmbeddertest, NoViewerPreferences) { - EXPECT_TRUE(OpenDocument("hello_world.pdf")); - - FPDF_DOCUMENT output_doc = FPDF_CreateNewDocument(); - EXPECT_TRUE(output_doc); - EXPECT_FALSE(FPDF_CopyViewerPreferences(output_doc, document())); - FPDF_CloseDocument(output_doc); -} - -TEST_F(FPDFPPOEmbeddertest, ViewerPreferences) { - EXPECT_TRUE(OpenDocument("viewer_ref.pdf")); - - FPDF_DOCUMENT output_doc = FPDF_CreateNewDocument(); - EXPECT_TRUE(output_doc); - EXPECT_TRUE(FPDF_CopyViewerPreferences(output_doc, document())); - FPDF_CloseDocument(output_doc); -} - -TEST_F(FPDFPPOEmbeddertest, ImportPages) { - ASSERT_TRUE(OpenDocument("viewer_ref.pdf")); - - FPDF_PAGE page = LoadPage(0); - EXPECT_TRUE(page); - - FPDF_DOCUMENT output_doc = FPDF_CreateNewDocument(); - ASSERT_TRUE(output_doc); - EXPECT_TRUE(FPDF_CopyViewerPreferences(output_doc, document())); - EXPECT_TRUE(FPDF_ImportPages(output_doc, document(), "1", 0)); - EXPECT_EQ(1, FPDF_GetPageCount(output_doc)); - FPDF_CloseDocument(output_doc); - - UnloadPage(page); -} - -TEST_F(FPDFPPOEmbeddertest, ImportNPages) { - ASSERT_TRUE(OpenDocument("rectangles_multi_pages.pdf")); - - std::unique_ptr output_doc_2up( - FPDF_ImportNPagesToOne(document(), 612, 792, 2, 1)); - ASSERT_TRUE(output_doc_2up); - EXPECT_EQ(3, FPDF_GetPageCount(output_doc_2up.get())); - std::unique_ptr output_doc_5up( - FPDF_ImportNPagesToOne(document(), 612, 792, 5, 1)); - ASSERT_TRUE(output_doc_5up); - EXPECT_EQ(1, FPDF_GetPageCount(output_doc_5up.get())); - std::unique_ptr output_doc_8up( - FPDF_ImportNPagesToOne(document(), 792, 612, 8, 1)); - ASSERT_TRUE(output_doc_8up); - EXPECT_EQ(1, FPDF_GetPageCount(output_doc_8up.get())); - std::unique_ptr output_doc_128up( - FPDF_ImportNPagesToOne(document(), 792, 612, 128, 1)); - ASSERT_TRUE(output_doc_128up); - EXPECT_EQ(1, FPDF_GetPageCount(output_doc_128up.get())); -} - -TEST_F(FPDFPPOEmbeddertest, BadNupParams) { - ASSERT_TRUE(OpenDocument("rectangles_multi_pages.pdf")); - - FPDF_DOCUMENT output_doc_zero_row = - FPDF_ImportNPagesToOne(document(), 612, 792, 0, 3); - ASSERT_FALSE(output_doc_zero_row); - FPDF_DOCUMENT output_doc_zero_col = - FPDF_ImportNPagesToOne(document(), 612, 792, 2, 0); - ASSERT_FALSE(output_doc_zero_col); - FPDF_DOCUMENT output_doc_zero_width = - FPDF_ImportNPagesToOne(document(), 0, 792, 2, 1); - ASSERT_FALSE(output_doc_zero_width); - FPDF_DOCUMENT output_doc_zero_height = - FPDF_ImportNPagesToOne(document(), 612, 0, 7, 1); - ASSERT_FALSE(output_doc_zero_height); -} - -// TODO(Xlou): Add more tests to check output doc content of -// FPDF_ImportNPagesToOne() -TEST_F(FPDFPPOEmbeddertest, NupRenderImage) { - ASSERT_TRUE(OpenDocument("rectangles_multi_pages.pdf")); - const int kPageCount = 2; - constexpr const char* kExpectedMD5s[kPageCount] = { - "4d225b961da0f1bced7c83273e64c9b6", "fb18142190d770cfbc329d2b071aee4d"}; - std::unique_ptr output_doc_3up( - FPDF_ImportNPagesToOne(document(), 792, 612, 3, 1)); - ASSERT_TRUE(output_doc_3up); - ASSERT_EQ(kPageCount, FPDF_GetPageCount(output_doc_3up.get())); - for (int i = 0; i < kPageCount; ++i) { - std::unique_ptr page( - FPDF_LoadPage(output_doc_3up.get(), i)); - ASSERT_TRUE(page); - std::unique_ptr bitmap( - RenderPageWithFlags(page.get(), nullptr, 0)); - EXPECT_EQ(792, FPDFBitmap_GetWidth(bitmap.get())); - EXPECT_EQ(612, FPDFBitmap_GetHeight(bitmap.get())); - EXPECT_EQ(kExpectedMD5s[i], HashBitmap(bitmap.get())); - } -} - -TEST_F(FPDFPPOEmbeddertest, BadRepeatViewerPref) { - ASSERT_TRUE(OpenDocument("repeat_viewer_ref.pdf")); - - FPDF_DOCUMENT output_doc = FPDF_CreateNewDocument(); - EXPECT_TRUE(output_doc); - EXPECT_TRUE(FPDF_CopyViewerPreferences(output_doc, document())); - - FPDF_FILEWRITE writer; - writer.version = 1; - writer.WriteBlock = FakeBlockWriter; - - EXPECT_TRUE(FPDF_SaveAsCopy(output_doc, &writer, 0)); - FPDF_CloseDocument(output_doc); -} - -TEST_F(FPDFPPOEmbeddertest, BadCircularViewerPref) { - ASSERT_TRUE(OpenDocument("circular_viewer_ref.pdf")); - - FPDF_DOCUMENT output_doc = FPDF_CreateNewDocument(); - EXPECT_TRUE(output_doc); - EXPECT_TRUE(FPDF_CopyViewerPreferences(output_doc, document())); - - FPDF_FILEWRITE writer; - writer.version = 1; - writer.WriteBlock = FakeBlockWriter; - - EXPECT_TRUE(FPDF_SaveAsCopy(output_doc, &writer, 0)); - FPDF_CloseDocument(output_doc); -} - -TEST_F(FPDFPPOEmbeddertest, BadRanges) { - EXPECT_TRUE(OpenDocument("viewer_ref.pdf")); - - FPDF_PAGE page = LoadPage(0); - EXPECT_TRUE(page); - - FPDF_DOCUMENT output_doc = FPDF_CreateNewDocument(); - EXPECT_TRUE(output_doc); - EXPECT_FALSE(FPDF_ImportPages(output_doc, document(), "clams", 0)); - EXPECT_FALSE(FPDF_ImportPages(output_doc, document(), "0", 0)); - EXPECT_FALSE(FPDF_ImportPages(output_doc, document(), "42", 0)); - EXPECT_FALSE(FPDF_ImportPages(output_doc, document(), "1,2", 0)); - EXPECT_FALSE(FPDF_ImportPages(output_doc, document(), "1-2", 0)); - EXPECT_FALSE(FPDF_ImportPages(output_doc, document(), ",1", 0)); - EXPECT_FALSE(FPDF_ImportPages(output_doc, document(), "1,", 0)); - EXPECT_FALSE(FPDF_ImportPages(output_doc, document(), "1-", 0)); - EXPECT_FALSE(FPDF_ImportPages(output_doc, document(), "-1", 0)); - EXPECT_FALSE(FPDF_ImportPages(output_doc, document(), "-,0,,,1-", 0)); - FPDF_CloseDocument(output_doc); - - UnloadPage(page); -} - -TEST_F(FPDFPPOEmbeddertest, GoodRanges) { - EXPECT_TRUE(OpenDocument("viewer_ref.pdf")); - - FPDF_PAGE page = LoadPage(0); - EXPECT_TRUE(page); - - FPDF_DOCUMENT output_doc = FPDF_CreateNewDocument(); - EXPECT_TRUE(output_doc); - EXPECT_TRUE(FPDF_CopyViewerPreferences(output_doc, document())); - EXPECT_TRUE(FPDF_ImportPages(output_doc, document(), "1,1,1,1", 0)); - EXPECT_TRUE(FPDF_ImportPages(output_doc, document(), "1-1", 0)); - EXPECT_EQ(5, FPDF_GetPageCount(output_doc)); - FPDF_CloseDocument(output_doc); - - UnloadPage(page); -} - -TEST_F(FPDFPPOEmbeddertest, BUG_664284) { - EXPECT_TRUE(OpenDocument("bug_664284.pdf")); - - FPDF_PAGE page = LoadPage(0); - ASSERT_NE(nullptr, page); - - FPDF_DOCUMENT output_doc = FPDF_CreateNewDocument(); - EXPECT_TRUE(output_doc); - EXPECT_TRUE(FPDF_ImportPages(output_doc, document(), "1", 0)); - FPDF_CloseDocument(output_doc); - - UnloadPage(page); -} - -TEST_F(FPDFPPOEmbeddertest, BUG_750568) { - const char* const kHashes[] = { - "64ad08132a1c5a166768298c8a578f57", "83b83e2f6bc80707d0a917c7634140b9", - "913cd3723a451e4e46fbc2c05702d1ee", "81fb7cfd4860f855eb468f73dfeb6d60"}; - - ASSERT_TRUE(OpenDocument("bug_750568.pdf")); - ASSERT_EQ(4, FPDF_GetPageCount(document())); - - for (size_t i = 0; i < 4; ++i) { - FPDF_PAGE page = LoadPage(i); - ASSERT_TRUE(page); - - std::unique_ptr bitmap = RenderLoadedPage(page); - ASSERT_EQ(200, FPDFBitmap_GetWidth(bitmap.get())); - ASSERT_EQ(200, FPDFBitmap_GetHeight(bitmap.get())); - ASSERT_EQ(800, FPDFBitmap_GetStride(bitmap.get())); - - EXPECT_EQ(kHashes[i], HashBitmap(bitmap.get())); - UnloadPage(page); - } - - FPDF_DOCUMENT output_doc = FPDF_CreateNewDocument(); - ASSERT_TRUE(output_doc); - EXPECT_TRUE(FPDF_ImportPages(output_doc, document(), "1,2,3,4", 0)); - ASSERT_EQ(4, FPDF_GetPageCount(output_doc)); - for (size_t i = 0; i < 4; ++i) { - FPDF_PAGE page = FPDF_LoadPage(output_doc, i); - ASSERT_TRUE(page); - - std::unique_ptr bitmap = - RenderPageWithFlags(page, nullptr, 0); - ASSERT_EQ(200, FPDFBitmap_GetWidth(bitmap.get())); - ASSERT_EQ(200, FPDFBitmap_GetHeight(bitmap.get())); - ASSERT_EQ(800, FPDFBitmap_GetStride(bitmap.get())); - - EXPECT_EQ(kHashes[i], HashBitmap(bitmap.get())); - FPDF_ClosePage(page); - } - FPDF_CloseDocument(output_doc); -} - -TEST_F(FPDFPPOEmbeddertest, ImportWithZeroLengthStream) { - EXPECT_TRUE(OpenDocument("zero_length_stream.pdf")); - FPDF_PAGE page = LoadPage(0); - ASSERT_TRUE(page); - - std::unique_ptr bitmap = RenderLoadedPage(page); - ASSERT_EQ(200, FPDFBitmap_GetWidth(bitmap.get())); - ASSERT_EQ(200, FPDFBitmap_GetHeight(bitmap.get())); - ASSERT_EQ(800, FPDFBitmap_GetStride(bitmap.get())); - - std::string digest = HashBitmap(bitmap.get()); - UnloadPage(page); - - FPDF_DOCUMENT new_doc = FPDF_CreateNewDocument(); - EXPECT_TRUE(new_doc); - EXPECT_TRUE(FPDF_ImportPages(new_doc, document(), "1", 0)); - - EXPECT_EQ(1, FPDF_GetPageCount(new_doc)); - FPDF_PAGE new_page = FPDF_LoadPage(new_doc, 0); - ASSERT_NE(nullptr, new_page); - std::unique_ptr new_bitmap = - RenderPageWithFlags(new_page, nullptr, 0); - ASSERT_EQ(200, FPDFBitmap_GetWidth(new_bitmap.get())); - ASSERT_EQ(200, FPDFBitmap_GetHeight(new_bitmap.get())); - ASSERT_EQ(800, FPDFBitmap_GetStride(new_bitmap.get())); - - EXPECT_EQ(digest, HashBitmap(new_bitmap.get())); - FPDF_ClosePage(new_page); - FPDF_CloseDocument(new_doc); -} diff --git a/fpdfsdk/fpdfsave.cpp b/fpdfsdk/fpdfsave.cpp deleted file mode 100644 index da3e12fc9c..0000000000 --- a/fpdfsdk/fpdfsave.cpp +++ /dev/null @@ -1,287 +0,0 @@ -// Copyright 2014 PDFium Authors. All rights reserved. -// 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_save.h" - -#include -#include -#include - -#include "core/fpdfapi/edit/cpdf_creator.h" -#include "core/fpdfapi/parser/cpdf_array.h" -#include "core/fpdfapi/parser/cpdf_document.h" -#include "core/fpdfapi/parser/cpdf_reference.h" -#include "core/fpdfapi/parser/cpdf_stream_acc.h" -#include "core/fpdfapi/parser/cpdf_string.h" -#include "core/fxcrt/cfx_memorystream.h" -#include "core/fxcrt/fx_extension.h" -#include "fpdfsdk/cpdfsdk_filewriteadapter.h" -#include "fpdfsdk/cpdfsdk_helpers.h" -#include "public/fpdf_edit.h" - -#ifdef PDF_ENABLE_XFA -#include "core/fxcrt/cfx_checksumcontext.h" -#include "fpdfsdk/fpdfxfa/cpdfxfa_context.h" -#include "fpdfsdk/fpdfxfa/cxfa_fwladaptertimermgr.h" -#include "public/fpdf_formfill.h" -#include "xfa/fxfa/cxfa_eventparam.h" -#include "xfa/fxfa/cxfa_ffapp.h" -#include "xfa/fxfa/cxfa_ffdocview.h" -#include "xfa/fxfa/cxfa_ffwidgethandler.h" -#include "xfa/fxfa/cxfa_readynodeiterator.h" -#include "xfa/fxfa/parser/cxfa_object.h" -#endif - -#if _FX_OS_ == _FX_OS_ANDROID_ -#include -#else -#include -#endif - -namespace { - -#ifdef PDF_ENABLE_XFA -bool SaveXFADocumentData(CPDFXFA_Context* pContext, - std::vector>* fileList) { - if (!pContext) - return false; - - if (!pContext->ContainsXFAForm()) - return true; - - CXFA_FFDocView* pXFADocView = pContext->GetXFADocView(); - if (!pXFADocView) - return true; - - CPDF_Document* pPDFDocument = pContext->GetPDFDoc(); - if (!pPDFDocument) - return false; - - const CPDF_Dictionary* pRoot = pPDFDocument->GetRoot(); - if (!pRoot) - return false; - - CPDF_Dictionary* pAcroForm = pRoot->GetDictFor("AcroForm"); - if (!pAcroForm) - return false; - - CPDF_Object* pXFA = pAcroForm->GetObjectFor("XFA"); - if (!pXFA) - return true; - - CPDF_Array* pArray = pXFA->AsArray(); - if (!pArray) - return false; - - int size = pArray->GetCount(); - int iFormIndex = -1; - int iDataSetsIndex = -1; - int iTemplate = -1; - int iLast = size - 2; - for (int i = 0; i < size - 1; i++) { - CPDF_Object* pPDFObj = pArray->GetObjectAt(i); - if (!pPDFObj->IsString()) - continue; - if (pPDFObj->GetString() == "form") - iFormIndex = i + 1; - else if (pPDFObj->GetString() == "datasets") - iDataSetsIndex = i + 1; - else if (pPDFObj->GetString() == "template") - iTemplate = i + 1; - } - auto pChecksum = pdfium::MakeUnique(); - pChecksum->StartChecksum(); - - // template - if (iTemplate > -1) { - CPDF_Stream* pTemplateStream = pArray->GetStreamAt(iTemplate); - auto pAcc = pdfium::MakeRetain(pTemplateStream); - pAcc->LoadAllDataFiltered(); - RetainPtr pTemplate = - pdfium::MakeRetain( - const_cast(pAcc->GetData()), pAcc->GetSize(), false); - pChecksum->UpdateChecksum(pTemplate); - } - CPDF_Stream* pFormStream = nullptr; - CPDF_Stream* pDataSetsStream = nullptr; - if (iFormIndex != -1) { - // Get form CPDF_Stream - CPDF_Object* pFormPDFObj = pArray->GetObjectAt(iFormIndex); - if (pFormPDFObj->IsReference()) { - CPDF_Object* pFormDirectObj = pFormPDFObj->GetDirect(); - if (pFormDirectObj && pFormDirectObj->IsStream()) { - pFormStream = (CPDF_Stream*)pFormDirectObj; - } - } else if (pFormPDFObj->IsStream()) { - pFormStream = (CPDF_Stream*)pFormPDFObj; - } - } - - if (iDataSetsIndex != -1) { - // Get datasets CPDF_Stream - CPDF_Object* pDataSetsPDFObj = pArray->GetObjectAt(iDataSetsIndex); - if (pDataSetsPDFObj->IsReference()) { - CPDF_Reference* pDataSetsRefObj = (CPDF_Reference*)pDataSetsPDFObj; - CPDF_Object* pDataSetsDirectObj = pDataSetsRefObj->GetDirect(); - if (pDataSetsDirectObj && pDataSetsDirectObj->IsStream()) { - pDataSetsStream = (CPDF_Stream*)pDataSetsDirectObj; - } - } else if (pDataSetsPDFObj->IsStream()) { - pDataSetsStream = (CPDF_Stream*)pDataSetsPDFObj; - } - } - // L"datasets" - { - RetainPtr pDsfileWrite = - pdfium::MakeRetain(false); - CXFA_FFDoc* ffdoc = pXFADocView->GetDoc(); - if (ffdoc->SavePackage( - ToNode(ffdoc->GetXFADoc()->GetXFAObject(XFA_HASHCODE_Datasets)), - pDsfileWrite, nullptr) && - pDsfileWrite->GetSize() > 0) { - // Datasets - pChecksum->UpdateChecksum(pDsfileWrite); - pChecksum->FinishChecksum(); - auto pDataDict = pdfium::MakeUnique( - pPDFDocument->GetByteStringPool()); - if (iDataSetsIndex != -1) { - if (pDataSetsStream) { - pDataSetsStream->InitStreamFromFile(pDsfileWrite, - std::move(pDataDict)); - } - } else { - CPDF_Stream* pData = pPDFDocument->NewIndirect(); - pData->InitStreamFromFile(pDsfileWrite, std::move(pDataDict)); - iLast = pArray->GetCount() - 2; - pArray->InsertNewAt(iLast, "datasets", false); - pArray->InsertNewAt(iLast + 1, pPDFDocument, - pData->GetObjNum()); - } - fileList->push_back(std::move(pDsfileWrite)); - } - } - // L"form" - { - RetainPtr pfileWrite = - pdfium::MakeRetain(false); - - CXFA_FFDoc* ffdoc = pXFADocView->GetDoc(); - if (ffdoc->SavePackage( - ToNode(ffdoc->GetXFADoc()->GetXFAObject(XFA_HASHCODE_Form)), - pfileWrite, pChecksum.get()) && - pfileWrite->GetSize() > 0) { - auto pDataDict = pdfium::MakeUnique( - pPDFDocument->GetByteStringPool()); - if (iFormIndex != -1) { - if (pFormStream) - pFormStream->InitStreamFromFile(pfileWrite, std::move(pDataDict)); - } else { - CPDF_Stream* pData = pPDFDocument->NewIndirect(); - pData->InitStreamFromFile(pfileWrite, std::move(pDataDict)); - iLast = pArray->GetCount() - 2; - pArray->InsertNewAt(iLast, "form", false); - pArray->InsertNewAt(iLast + 1, pPDFDocument, - pData->GetObjNum()); - } - fileList->push_back(std::move(pfileWrite)); - } - } - return true; -} - -bool SendPostSaveToXFADoc(CPDFXFA_Context* pContext) { - if (!pContext) - return false; - - if (!pContext->ContainsXFAForm()) - return true; - - CXFA_FFDocView* pXFADocView = pContext->GetXFADocView(); - if (!pXFADocView) - return false; - - CXFA_FFWidgetHandler* pWidgetHandler = pXFADocView->GetWidgetHandler(); - auto it = pXFADocView->CreateReadyNodeIterator(); - while (CXFA_Node* pNode = it->MoveToNext()) { - CXFA_EventParam preParam; - preParam.m_eType = XFA_EVENT_PostSave; - pWidgetHandler->ProcessEvent(pNode, &preParam); - } - pXFADocView->UpdateDocView(); - pContext->ClearChangeMark(); - return true; -} - -bool SendPreSaveToXFADoc(CPDFXFA_Context* pContext, - std::vector>* fileList) { - if (!pContext->ContainsXFAForm()) - return true; - - CXFA_FFDocView* pXFADocView = pContext->GetXFADocView(); - if (!pXFADocView) - return true; - - CXFA_FFWidgetHandler* pWidgetHandler = pXFADocView->GetWidgetHandler(); - auto it = pXFADocView->CreateReadyNodeIterator(); - while (CXFA_Node* pNode = it->MoveToNext()) { - CXFA_EventParam preParam; - preParam.m_eType = XFA_EVENT_PreSave; - pWidgetHandler->ProcessEvent(pNode, &preParam); - } - pXFADocView->UpdateDocView(); - return SaveXFADocumentData(pContext, fileList); -} -#endif // PDF_ENABLE_XFA - -bool FPDF_Doc_Save(FPDF_DOCUMENT document, - FPDF_FILEWRITE* pFileWrite, - FPDF_DWORD flags, - FPDF_BOOL bSetVersion, - int fileVerion) { - CPDF_Document* pPDFDoc = CPDFDocumentFromFPDFDocument(document); - if (!pPDFDoc) - return 0; - -#ifdef PDF_ENABLE_XFA - CPDFXFA_Context* pContext = static_cast(document); - std::vector> fileList; - SendPreSaveToXFADoc(pContext, &fileList); -#endif // PDF_ENABLE_XFA - - if (flags < FPDF_INCREMENTAL || flags > FPDF_REMOVE_SECURITY) - flags = 0; - - CPDF_Creator fileMaker( - pPDFDoc, pdfium::MakeRetain(pFileWrite)); - if (bSetVersion) - fileMaker.SetFileVersion(fileVerion); - if (flags == FPDF_REMOVE_SECURITY) { - flags = 0; - fileMaker.RemoveSecurity(); - } - - bool bRet = fileMaker.Create(flags); -#ifdef PDF_ENABLE_XFA - SendPostSaveToXFADoc(pContext); -#endif // PDF_ENABLE_XFA - return bRet; -} - -} // namespace - -FPDF_EXPORT FPDF_BOOL FPDF_CALLCONV FPDF_SaveAsCopy(FPDF_DOCUMENT document, - FPDF_FILEWRITE* pFileWrite, - FPDF_DWORD flags) { - return FPDF_Doc_Save(document, pFileWrite, flags, false, 0); -} - -FPDF_EXPORT FPDF_BOOL FPDF_CALLCONV -FPDF_SaveWithVersion(FPDF_DOCUMENT document, - FPDF_FILEWRITE* pFileWrite, - FPDF_DWORD flags, - int fileVersion) { - return FPDF_Doc_Save(document, pFileWrite, flags, true, fileVersion); -} diff --git a/fpdfsdk/fpdfsave_embeddertest.cpp b/fpdfsdk/fpdfsave_embeddertest.cpp deleted file mode 100644 index e753ba0356..0000000000 --- a/fpdfsdk/fpdfsave_embeddertest.cpp +++ /dev/null @@ -1,101 +0,0 @@ -// Copyright 2016 PDFium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#include -#include - -#include "core/fxcrt/fx_string.h" -#include "public/fpdf_edit.h" -#include "public/fpdf_ppo.h" -#include "public/fpdf_save.h" -#include "public/fpdfview.h" -#include "testing/embedder_test.h" -#include "testing/gmock/include/gmock/gmock-matchers.h" -#include "testing/gtest/include/gtest/gtest.h" -#include "testing/test_support.h" - -class FPDFSaveEmbedderTest : public EmbedderTest {}; - -TEST_F(FPDFSaveEmbedderTest, SaveSimpleDoc) { - EXPECT_TRUE(OpenDocument("hello_world.pdf")); - EXPECT_TRUE(FPDF_SaveAsCopy(document(), this, 0)); - EXPECT_THAT(GetString(), testing::StartsWith("%PDF-1.7\r\n")); - EXPECT_EQ(805u, GetString().length()); -} - -TEST_F(FPDFSaveEmbedderTest, SaveSimpleDocWithVersion) { - EXPECT_TRUE(OpenDocument("hello_world.pdf")); - EXPECT_TRUE(FPDF_SaveWithVersion(document(), this, 0, 14)); - EXPECT_THAT(GetString(), testing::StartsWith("%PDF-1.4\r\n")); - EXPECT_EQ(805u, GetString().length()); -} -TEST_F(FPDFSaveEmbedderTest, SaveSimpleDocWithBadVersion) { - EXPECT_TRUE(OpenDocument("hello_world.pdf")); - EXPECT_TRUE(FPDF_SaveWithVersion(document(), this, 0, -1)); - EXPECT_THAT(GetString(), testing::StartsWith("%PDF-1.7\r\n")); - - ClearString(); - EXPECT_TRUE(FPDF_SaveWithVersion(document(), this, 0, 0)); - EXPECT_THAT(GetString(), testing::StartsWith("%PDF-1.7\r\n")); - - ClearString(); - EXPECT_TRUE(FPDF_SaveWithVersion(document(), this, 0, 18)); - EXPECT_THAT(GetString(), testing::StartsWith("%PDF-1.7\r\n")); -} - -TEST_F(FPDFSaveEmbedderTest, SaveCopiedDoc) { - EXPECT_TRUE(OpenDocument("hello_world.pdf")); - - FPDF_PAGE page = LoadPage(0); - EXPECT_TRUE(page); - - FPDF_DOCUMENT output_doc = FPDF_CreateNewDocument(); - EXPECT_TRUE(output_doc); - EXPECT_TRUE(FPDF_ImportPages(output_doc, document(), "1", 0)); - EXPECT_TRUE(FPDF_SaveAsCopy(output_doc, this, 0)); - FPDF_CloseDocument(output_doc); - - UnloadPage(page); -} - -TEST_F(FPDFSaveEmbedderTest, SaveLinearizedDoc) { - const int kPageCount = 3; - std::string original_md5[kPageCount]; - - EXPECT_TRUE(OpenDocument("linearized.pdf")); - for (int i = 0; i < kPageCount; ++i) { - FPDF_PAGE page = LoadPage(i); - ASSERT_TRUE(page); - std::unique_ptr bitmap = RenderLoadedPage(page); - EXPECT_EQ(612, FPDFBitmap_GetWidth(bitmap.get())); - EXPECT_EQ(792, FPDFBitmap_GetHeight(bitmap.get())); - original_md5[i] = HashBitmap(bitmap.get()); - UnloadPage(page); - } - - EXPECT_TRUE(FPDF_SaveAsCopy(document(), this, 0)); - EXPECT_THAT(GetString(), testing::StartsWith("%PDF-1.6\r\n")); - EXPECT_THAT(GetString(), testing::HasSubstr("/Root ")); - EXPECT_THAT(GetString(), testing::HasSubstr("/Info ")); - EXPECT_EQ(8219u, GetString().length()); - - // Make sure new document renders the same as the old one. - EXPECT_TRUE(OpenSavedDocument()); - for (int i = 0; i < kPageCount; ++i) { - FPDF_PAGE page = LoadSavedPage(i); - ASSERT_TRUE(page); - std::unique_ptr bitmap = RenderSavedPage(page); - EXPECT_EQ(original_md5[i], HashBitmap(bitmap.get())); - CloseSavedPage(page); - } - CloseSavedDocument(); -} - -TEST_F(FPDFSaveEmbedderTest, BUG_342) { - EXPECT_TRUE(OpenDocument("hello_world.pdf")); - EXPECT_TRUE(FPDF_SaveAsCopy(document(), this, 0)); - EXPECT_THAT(GetString(), testing::HasSubstr("0000000000 65535 f\r\n")); - EXPECT_THAT(GetString(), - testing::Not(testing::HasSubstr("0000000000 65536 f\r\n"))); -} diff --git a/fpdfsdk/fpdftext.cpp b/fpdfsdk/fpdftext.cpp deleted file mode 100644 index 7778696931..0000000000 --- a/fpdfsdk/fpdftext.cpp +++ /dev/null @@ -1,393 +0,0 @@ -// Copyright 2014 PDFium Authors. All rights reserved. -// 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_text.h" - -#include -#include - -#include "core/fpdfapi/page/cpdf_page.h" -#include "core/fpdfdoc/cpdf_viewerpreferences.h" -#include "core/fpdftext/cpdf_linkextract.h" -#include "core/fpdftext/cpdf_textpage.h" -#include "core/fpdftext/cpdf_textpagefind.h" -#include "fpdfsdk/cpdfsdk_helpers.h" -#include "third_party/base/numerics/safe_conversions.h" -#include "third_party/base/stl_util.h" - -#ifdef PDF_ENABLE_XFA -#include "fpdfsdk/fpdfxfa/cpdfxfa_context.h" -#include "fpdfsdk/fpdfxfa/cpdfxfa_page.h" -#endif // PDF_ENABLE_XFA - -#ifdef _WIN32 -#include -#endif - -namespace { - -constexpr size_t kBytesPerCharacter = sizeof(unsigned short); - -CPDF_TextPage* CPDFTextPageFromFPDFTextPage(FPDF_TEXTPAGE text_page) { - return static_cast(text_page); -} - -CPDF_TextPageFind* CPDFTextPageFindFromFPDFSchHandle(FPDF_SCHHANDLE handle) { - return static_cast(handle); -} - -CPDF_LinkExtract* CPDFLinkExtractFromFPDFPageLink(FPDF_PAGELINK link) { - return static_cast(link); -} - -} // namespace - -FPDF_EXPORT FPDF_TEXTPAGE FPDF_CALLCONV FPDFText_LoadPage(FPDF_PAGE page) { - CPDF_Page* pPDFPage = CPDFPageFromFPDFPage(page); - if (!pPDFPage) - return nullptr; - -#ifdef PDF_ENABLE_XFA - CPDFXFA_Page* pPage = (CPDFXFA_Page*)page; - CPDFXFA_Context* pContext = pPage->GetContext(); - CPDF_ViewerPreferences viewRef(pContext->GetPDFDoc()); -#else // PDF_ENABLE_XFA - CPDF_ViewerPreferences viewRef(pPDFPage->m_pDocument.Get()); -#endif // PDF_ENABLE_XFA - - CPDF_TextPage* textpage = new CPDF_TextPage( - pPDFPage, viewRef.IsDirectionR2L() ? FPDFText_Direction::Right - : FPDFText_Direction::Left); - textpage->ParseTextPage(); - return textpage; -} - -FPDF_EXPORT void FPDF_CALLCONV FPDFText_ClosePage(FPDF_TEXTPAGE text_page) { - delete CPDFTextPageFromFPDFTextPage(text_page); -} - -FPDF_EXPORT int FPDF_CALLCONV FPDFText_CountChars(FPDF_TEXTPAGE text_page) { - if (!text_page) - return -1; - - CPDF_TextPage* textpage = CPDFTextPageFromFPDFTextPage(text_page); - return textpage->CountChars(); -} - -FPDF_EXPORT unsigned int FPDF_CALLCONV -FPDFText_GetUnicode(FPDF_TEXTPAGE text_page, int index) { - if (!text_page) - return 0; - - CPDF_TextPage* textpage = CPDFTextPageFromFPDFTextPage(text_page); - if (index < 0 || index >= textpage->CountChars()) - return 0; - - FPDF_CHAR_INFO charinfo; - textpage->GetCharInfo(index, &charinfo); - return charinfo.m_Unicode; -} - -FPDF_EXPORT double FPDF_CALLCONV FPDFText_GetFontSize(FPDF_TEXTPAGE text_page, - int index) { - if (!text_page) - return 0; - CPDF_TextPage* textpage = CPDFTextPageFromFPDFTextPage(text_page); - - if (index < 0 || index >= textpage->CountChars()) - return 0; - - FPDF_CHAR_INFO charinfo; - textpage->GetCharInfo(index, &charinfo); - return charinfo.m_FontSize; -} - -FPDF_EXPORT FPDF_BOOL FPDF_CALLCONV FPDFText_GetCharBox(FPDF_TEXTPAGE text_page, - int index, - double* left, - double* right, - double* bottom, - double* top) { - if (!text_page || index < 0) - return false; - - CPDF_TextPage* textpage = CPDFTextPageFromFPDFTextPage(text_page); - if (index >= textpage->CountChars()) - return false; - - FPDF_CHAR_INFO charinfo; - textpage->GetCharInfo(index, &charinfo); - *left = charinfo.m_CharBox.left; - *right = charinfo.m_CharBox.right; - *bottom = charinfo.m_CharBox.bottom; - *top = charinfo.m_CharBox.top; - return true; -} - -FPDF_EXPORT FPDF_BOOL FPDF_CALLCONV -FPDFText_GetCharOrigin(FPDF_TEXTPAGE text_page, - int index, - double* x, - double* y) { - if (!text_page) - return false; - CPDF_TextPage* textpage = CPDFTextPageFromFPDFTextPage(text_page); - - if (index < 0 || index >= textpage->CountChars()) - return false; - FPDF_CHAR_INFO charinfo; - textpage->GetCharInfo(index, &charinfo); - *x = charinfo.m_Origin.x; - *y = charinfo.m_Origin.y; - return true; -} - -// select -FPDF_EXPORT int FPDF_CALLCONV -FPDFText_GetCharIndexAtPos(FPDF_TEXTPAGE text_page, - double x, - double y, - double xTolerance, - double yTolerance) { - if (!text_page) - return -3; - - CPDF_TextPage* textpage = CPDFTextPageFromFPDFTextPage(text_page); - return textpage->GetIndexAtPos( - CFX_PointF(static_cast(x), static_cast(y)), - CFX_SizeF(static_cast(xTolerance), - static_cast(yTolerance))); -} - -FPDF_EXPORT int FPDF_CALLCONV FPDFText_GetText(FPDF_TEXTPAGE page, - int char_start, - int char_count, - unsigned short* result) { - if (!page || char_start < 0 || char_count < 0 || !result) - return 0; - - CPDF_TextPage* textpage = CPDFTextPageFromFPDFTextPage(page); - int char_available = textpage->CountChars() - char_start; - if (char_available <= 0) - return 0; - - char_count = std::min(char_count, char_available); - if (char_count == 0) { - // Writing out "", which has a character count of 1 due to the NUL. - *result = '\0'; - return 1; - } - - WideString str = textpage->GetPageText(char_start, char_count); - - if (str.GetLength() > static_cast(char_count)) - str = str.Left(static_cast(char_count)); - - // UFT16LE_Encode doesn't handle surrogate pairs properly, so it is expected - // the number of items to stay the same. - ByteString byte_str = str.UTF16LE_Encode(); - size_t byte_str_len = byte_str.GetLength(); - int ret_count = byte_str_len / kBytesPerCharacter; - - ASSERT(ret_count <= char_count + 1); // +1 to account for the NUL terminator. - memcpy(result, byte_str.GetBuffer(byte_str_len), byte_str_len); - return ret_count; -} - -FPDF_EXPORT int FPDF_CALLCONV FPDFText_CountRects(FPDF_TEXTPAGE text_page, - int start, - int count) { - if (!text_page) - return 0; - - CPDF_TextPage* textpage = CPDFTextPageFromFPDFTextPage(text_page); - return textpage->CountRects(start, count); -} - -FPDF_EXPORT FPDF_BOOL FPDF_CALLCONV FPDFText_GetRect(FPDF_TEXTPAGE text_page, - int rect_index, - double* left, - double* top, - double* right, - double* bottom) { - if (!text_page) - return false; - - CPDF_TextPage* textpage = CPDFTextPageFromFPDFTextPage(text_page); - CFX_FloatRect rect; - bool result = textpage->GetRect(rect_index, &rect); - - *left = rect.left; - *top = rect.top; - *right = rect.right; - *bottom = rect.bottom; - return result; -} - -FPDF_EXPORT int FPDF_CALLCONV FPDFText_GetBoundedText(FPDF_TEXTPAGE text_page, - double left, - double top, - double right, - double bottom, - unsigned short* buffer, - int buflen) { - if (!text_page) - return 0; - - CPDF_TextPage* textpage = CPDFTextPageFromFPDFTextPage(text_page); - CFX_FloatRect rect((float)left, (float)bottom, (float)right, (float)top); - WideString str = textpage->GetTextByRect(rect); - - if (buflen <= 0 || !buffer) - return str.GetLength(); - - ByteString cbUTF16Str = str.UTF16LE_Encode(); - int len = cbUTF16Str.GetLength() / sizeof(unsigned short); - int size = buflen > len ? len : buflen; - memcpy(buffer, cbUTF16Str.GetBuffer(size * sizeof(unsigned short)), - size * sizeof(unsigned short)); - cbUTF16Str.ReleaseBuffer(size * sizeof(unsigned short)); - - return size; -} - -// Search -// -1 for end -FPDF_EXPORT FPDF_SCHHANDLE FPDF_CALLCONV -FPDFText_FindStart(FPDF_TEXTPAGE text_page, - FPDF_WIDESTRING findwhat, - unsigned long flags, - int start_index) { - if (!text_page) - return nullptr; - - CPDF_TextPageFind* textpageFind = - new CPDF_TextPageFind(CPDFTextPageFromFPDFTextPage(text_page)); - size_t len = WideString::WStringLength(findwhat); - textpageFind->FindFirst( - WideString::FromUTF16LE(findwhat, len), flags, - start_index >= 0 ? Optional(start_index) : Optional()); - return textpageFind; -} - -FPDF_EXPORT FPDF_BOOL FPDF_CALLCONV FPDFText_FindNext(FPDF_SCHHANDLE handle) { - if (!handle) - return false; - - CPDF_TextPageFind* textpageFind = CPDFTextPageFindFromFPDFSchHandle(handle); - return textpageFind->FindNext(); -} - -FPDF_EXPORT FPDF_BOOL FPDF_CALLCONV FPDFText_FindPrev(FPDF_SCHHANDLE handle) { - if (!handle) - return false; - - CPDF_TextPageFind* textpageFind = CPDFTextPageFindFromFPDFSchHandle(handle); - return textpageFind->FindPrev(); -} - -FPDF_EXPORT int FPDF_CALLCONV -FPDFText_GetSchResultIndex(FPDF_SCHHANDLE handle) { - if (!handle) - return 0; - - CPDF_TextPageFind* textpageFind = CPDFTextPageFindFromFPDFSchHandle(handle); - return textpageFind->GetCurOrder(); -} - -FPDF_EXPORT int FPDF_CALLCONV FPDFText_GetSchCount(FPDF_SCHHANDLE handle) { - if (!handle) - return 0; - - CPDF_TextPageFind* textpageFind = CPDFTextPageFindFromFPDFSchHandle(handle); - return textpageFind->GetMatchedCount(); -} - -FPDF_EXPORT void FPDF_CALLCONV FPDFText_FindClose(FPDF_SCHHANDLE handle) { - if (!handle) - return; - - CPDF_TextPageFind* textpageFind = CPDFTextPageFindFromFPDFSchHandle(handle); - delete textpageFind; - handle = nullptr; -} - -// web link -FPDF_EXPORT FPDF_PAGELINK FPDF_CALLCONV -FPDFLink_LoadWebLinks(FPDF_TEXTPAGE text_page) { - if (!text_page) - return nullptr; - - CPDF_LinkExtract* pageLink = - new CPDF_LinkExtract(CPDFTextPageFromFPDFTextPage(text_page)); - pageLink->ExtractLinks(); - return pageLink; -} - -FPDF_EXPORT int FPDF_CALLCONV FPDFLink_CountWebLinks(FPDF_PAGELINK link_page) { - if (!link_page) - return 0; - - CPDF_LinkExtract* pageLink = CPDFLinkExtractFromFPDFPageLink(link_page); - return pdfium::base::checked_cast(pageLink->CountLinks()); -} - -FPDF_EXPORT int FPDF_CALLCONV FPDFLink_GetURL(FPDF_PAGELINK link_page, - int link_index, - unsigned short* buffer, - int buflen) { - WideString wsUrl(L""); - if (link_page && link_index >= 0) { - CPDF_LinkExtract* pageLink = CPDFLinkExtractFromFPDFPageLink(link_page); - wsUrl = pageLink->GetURL(link_index); - } - ByteString cbUTF16URL = wsUrl.UTF16LE_Encode(); - int required = cbUTF16URL.GetLength() / sizeof(unsigned short); - if (!buffer || buflen <= 0) - return required; - - int size = std::min(required, buflen); - if (size > 0) { - int buf_size = size * sizeof(unsigned short); - memcpy(buffer, cbUTF16URL.GetBuffer(buf_size), buf_size); - } - return size; -} - -FPDF_EXPORT int FPDF_CALLCONV FPDFLink_CountRects(FPDF_PAGELINK link_page, - int link_index) { - if (!link_page || link_index < 0) - return 0; - - CPDF_LinkExtract* pageLink = CPDFLinkExtractFromFPDFPageLink(link_page); - return pdfium::CollectionSize(pageLink->GetRects(link_index)); -} - -FPDF_EXPORT FPDF_BOOL FPDF_CALLCONV FPDFLink_GetRect(FPDF_PAGELINK link_page, - int link_index, - int rect_index, - double* left, - double* top, - double* right, - double* bottom) { - if (!link_page || link_index < 0 || rect_index < 0) - return false; - - CPDF_LinkExtract* pageLink = CPDFLinkExtractFromFPDFPageLink(link_page); - std::vector rectArray = pageLink->GetRects(link_index); - if (rect_index >= pdfium::CollectionSize(rectArray)) - return false; - - *left = rectArray[rect_index].left; - *right = rectArray[rect_index].right; - *top = rectArray[rect_index].top; - *bottom = rectArray[rect_index].bottom; - return true; -} - -FPDF_EXPORT void FPDF_CALLCONV FPDFLink_CloseWebLinks(FPDF_PAGELINK link_page) { - delete CPDFLinkExtractFromFPDFPageLink(link_page); -} diff --git a/fpdfsdk/fpdftext_embeddertest.cpp b/fpdfsdk/fpdftext_embeddertest.cpp deleted file mode 100644 index 9d09381d7c..0000000000 --- a/fpdfsdk/fpdftext_embeddertest.cpp +++ /dev/null @@ -1,756 +0,0 @@ -// Copyright 2015 PDFium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#include - -#include "core/fxcrt/fx_memory.h" -#include "public/fpdf_text.h" -#include "public/fpdfview.h" -#include "testing/embedder_test.h" -#include "testing/gtest/include/gtest/gtest.h" -#include "testing/test_support.h" - -namespace { - -bool check_unsigned_shorts(const char* expected, - const unsigned short* actual, - size_t length) { - if (length > strlen(expected) + 1) - return false; - - for (size_t i = 0; i < length; ++i) { - if (actual[i] != static_cast(expected[i])) - return false; - } - return true; -} - -} // namespace - -class FPDFTextEmbeddertest : public EmbedderTest {}; - -TEST_F(FPDFTextEmbeddertest, Text) { - ASSERT_TRUE(OpenDocument("hello_world.pdf")); - FPDF_PAGE page = LoadPage(0); - ASSERT_TRUE(page); - - FPDF_TEXTPAGE textpage = FPDFText_LoadPage(page); - ASSERT_TRUE(textpage); - - static const char kExpected[] = "Hello, world!\r\nGoodbye, world!"; - unsigned short buffer[128]; - memset(buffer, 0xbd, sizeof(buffer)); - - // Check that edge cases are handled gracefully - EXPECT_EQ(0, FPDFText_GetText(textpage, 0, 128, nullptr)); - EXPECT_EQ(0, FPDFText_GetText(textpage, -1, 128, buffer)); - EXPECT_EQ(0, FPDFText_GetText(textpage, 0, -1, buffer)); - EXPECT_EQ(1, FPDFText_GetText(textpage, 0, 0, buffer)); - EXPECT_EQ(0, buffer[0]); - - // Keep going and check the next case. - memset(buffer, 0xbd, sizeof(buffer)); - EXPECT_EQ(2, FPDFText_GetText(textpage, 0, 1, buffer)); - EXPECT_EQ(kExpected[0], buffer[0]); - EXPECT_EQ(0, buffer[1]); - - // Check includes the terminating NUL that is provided. - int num_chars = FPDFText_GetText(textpage, 0, 128, buffer); - ASSERT_GE(num_chars, 0); - EXPECT_EQ(sizeof(kExpected), static_cast(num_chars)); - EXPECT_TRUE(check_unsigned_shorts(kExpected, buffer, sizeof(kExpected))); - - // Count does not include the terminating NUL in the string literal. - EXPECT_EQ(sizeof(kExpected) - 1, - static_cast(FPDFText_CountChars(textpage))); - for (size_t i = 0; i < sizeof(kExpected) - 1; ++i) { - EXPECT_EQ(static_cast(kExpected[i]), - FPDFText_GetUnicode(textpage, i)) - << " at " << i; - } - - // Extracting using a buffer that will be completely filled. Small buffer is - // 12 elements long, since it will need 2 locations per displayed character in - // the expected string, plus 2 more for the terminating character. - static const char kSmallExpected[] = "Hello"; - unsigned short small_buffer[12]; - memset(buffer, 0xbd, sizeof(buffer)); - EXPECT_EQ(6, FPDFText_GetText(textpage, 0, 5, small_buffer)); - EXPECT_TRUE(check_unsigned_shorts(kSmallExpected, small_buffer, - sizeof(kSmallExpected))); - - EXPECT_EQ(12.0, FPDFText_GetFontSize(textpage, 0)); - EXPECT_EQ(16.0, FPDFText_GetFontSize(textpage, 15)); - - double left = 0.0; - double right = 0.0; - double bottom = 0.0; - double top = 0.0; - EXPECT_FALSE(FPDFText_GetCharBox(nullptr, 4, &left, &right, &bottom, &top)); - EXPECT_DOUBLE_EQ(0.0, left); - EXPECT_DOUBLE_EQ(0.0, right); - EXPECT_DOUBLE_EQ(0.0, bottom); - EXPECT_DOUBLE_EQ(0.0, top); - EXPECT_FALSE(FPDFText_GetCharBox(textpage, -1, &left, &right, &bottom, &top)); - EXPECT_DOUBLE_EQ(0.0, left); - EXPECT_DOUBLE_EQ(0.0, right); - EXPECT_DOUBLE_EQ(0.0, bottom); - EXPECT_DOUBLE_EQ(0.0, top); - EXPECT_FALSE(FPDFText_GetCharBox(textpage, 55, &left, &right, &bottom, &top)); - EXPECT_DOUBLE_EQ(0.0, left); - EXPECT_DOUBLE_EQ(0.0, right); - EXPECT_DOUBLE_EQ(0.0, bottom); - EXPECT_DOUBLE_EQ(0.0, top); - - EXPECT_TRUE(FPDFText_GetCharBox(textpage, 4, &left, &right, &bottom, &top)); - EXPECT_NEAR(41.071, left, 0.001); - EXPECT_NEAR(46.243, right, 0.001); - EXPECT_NEAR(49.844, bottom, 0.001); - EXPECT_NEAR(55.520, top, 0.001); - - double x = 0.0; - double y = 0.0; - EXPECT_TRUE(FPDFText_GetCharOrigin(textpage, 4, &x, &y)); - EXPECT_NEAR(40.664, x, 0.001); - EXPECT_NEAR(50.000, y, 0.001); - - EXPECT_EQ(4, FPDFText_GetCharIndexAtPos(textpage, 42.0, 50.0, 1.0, 1.0)); - EXPECT_EQ(-1, FPDFText_GetCharIndexAtPos(textpage, 0.0, 0.0, 1.0, 1.0)); - EXPECT_EQ(-1, FPDFText_GetCharIndexAtPos(textpage, 199.0, 199.0, 1.0, 1.0)); - - // Test out of range indicies. - EXPECT_EQ(-1, - FPDFText_GetCharIndexAtPos(textpage, 42.0, 10000000.0, 1.0, 1.0)); - EXPECT_EQ(-1, FPDFText_GetCharIndexAtPos(textpage, -1.0, 50.0, 1.0, 1.0)); - - // Count does not include the terminating NUL in the string literal. - EXPECT_EQ(2, FPDFText_CountRects(textpage, 0, sizeof(kExpected) - 1)); - - left = 0.0; - right = 0.0; - bottom = 0.0; - top = 0.0; - EXPECT_TRUE(FPDFText_GetRect(textpage, 1, &left, &top, &right, &bottom)); - EXPECT_NEAR(20.847, left, 0.001); - EXPECT_NEAR(135.167, right, 0.001); - EXPECT_NEAR(96.655, bottom, 0.001); - EXPECT_NEAR(116.000, top, 0.001); - - // Test out of range indicies set outputs to (0.0, 0.0, 0.0, 0.0). - left = -1.0; - right = -1.0; - bottom = -1.0; - top = -1.0; - EXPECT_FALSE(FPDFText_GetRect(textpage, -1, &left, &top, &right, &bottom)); - EXPECT_EQ(0.0, left); - EXPECT_EQ(0.0, right); - EXPECT_EQ(0.0, bottom); - EXPECT_EQ(0.0, top); - - left = -2.0; - right = -2.0; - bottom = -2.0; - top = -2.0; - EXPECT_FALSE(FPDFText_GetRect(textpage, 2, &left, &top, &right, &bottom)); - EXPECT_EQ(0.0, left); - EXPECT_EQ(0.0, right); - EXPECT_EQ(0.0, bottom); - EXPECT_EQ(0.0, top); - - EXPECT_EQ(9, FPDFText_GetBoundedText(textpage, 41.0, 56.0, 82.0, 48.0, 0, 0)); - - // Extract starting at character 4 as above. - memset(buffer, 0xbd, sizeof(buffer)); - EXPECT_EQ( - 1, FPDFText_GetBoundedText(textpage, 41.0, 56.0, 82.0, 48.0, buffer, 1)); - EXPECT_TRUE(check_unsigned_shorts(kExpected + 4, buffer, 1)); - EXPECT_EQ(0xbdbd, buffer[1]); - - memset(buffer, 0xbd, sizeof(buffer)); - EXPECT_EQ( - 9, FPDFText_GetBoundedText(textpage, 41.0, 56.0, 82.0, 48.0, buffer, 9)); - EXPECT_TRUE(check_unsigned_shorts(kExpected + 4, buffer, 9)); - EXPECT_EQ(0xbdbd, buffer[9]); - - memset(buffer, 0xbd, sizeof(buffer)); - EXPECT_EQ(10, FPDFText_GetBoundedText(textpage, 41.0, 56.0, 82.0, 48.0, - buffer, 128)); - EXPECT_TRUE(check_unsigned_shorts(kExpected + 4, buffer, 9)); - EXPECT_EQ(0u, buffer[9]); - EXPECT_EQ(0xbdbd, buffer[10]); - - FPDFText_ClosePage(textpage); - UnloadPage(page); -} - -TEST_F(FPDFTextEmbeddertest, TextSearch) { - ASSERT_TRUE(OpenDocument("hello_world.pdf")); - FPDF_PAGE page = LoadPage(0); - ASSERT_TRUE(page); - - FPDF_TEXTPAGE textpage = FPDFText_LoadPage(page); - ASSERT_TRUE(textpage); - - std::unique_ptr nope = - GetFPDFWideString(L"nope"); - std::unique_ptr world = - GetFPDFWideString(L"world"); - std::unique_ptr world_caps = - GetFPDFWideString(L"WORLD"); - std::unique_ptr world_substr = - GetFPDFWideString(L"orld"); - - // No occurences of "nope" in test page. - FPDF_SCHHANDLE search = FPDFText_FindStart(textpage, nope.get(), 0, 0); - EXPECT_TRUE(search); - EXPECT_EQ(0, FPDFText_GetSchResultIndex(search)); - EXPECT_EQ(0, FPDFText_GetSchCount(search)); - - // Advancing finds nothing. - EXPECT_FALSE(FPDFText_FindNext(search)); - EXPECT_EQ(0, FPDFText_GetSchResultIndex(search)); - EXPECT_EQ(0, FPDFText_GetSchCount(search)); - - // Retreating finds nothing. - EXPECT_FALSE(FPDFText_FindPrev(search)); - EXPECT_EQ(0, FPDFText_GetSchResultIndex(search)); - EXPECT_EQ(0, FPDFText_GetSchCount(search)); - FPDFText_FindClose(search); - - // Two occurences of "world" in test page. - search = FPDFText_FindStart(textpage, world.get(), 0, 2); - EXPECT_TRUE(search); - - // Remains not found until advanced. - EXPECT_EQ(0, FPDFText_GetSchResultIndex(search)); - EXPECT_EQ(0, FPDFText_GetSchCount(search)); - - // First occurence of "world" in this test page. - EXPECT_TRUE(FPDFText_FindNext(search)); - EXPECT_EQ(7, FPDFText_GetSchResultIndex(search)); - EXPECT_EQ(5, FPDFText_GetSchCount(search)); - - // Last occurence of "world" in this test page. - EXPECT_TRUE(FPDFText_FindNext(search)); - EXPECT_EQ(24, FPDFText_GetSchResultIndex(search)); - EXPECT_EQ(5, FPDFText_GetSchCount(search)); - - // Found position unchanged when fails to advance. - EXPECT_FALSE(FPDFText_FindNext(search)); - EXPECT_EQ(24, FPDFText_GetSchResultIndex(search)); - EXPECT_EQ(5, FPDFText_GetSchCount(search)); - - // Back to first occurence. - EXPECT_TRUE(FPDFText_FindPrev(search)); - EXPECT_EQ(7, FPDFText_GetSchResultIndex(search)); - EXPECT_EQ(5, FPDFText_GetSchCount(search)); - - // Found position unchanged when fails to retreat. - EXPECT_FALSE(FPDFText_FindPrev(search)); - EXPECT_EQ(7, FPDFText_GetSchResultIndex(search)); - EXPECT_EQ(5, FPDFText_GetSchCount(search)); - FPDFText_FindClose(search); - - // Exact search unaffected by case sensitiity and whole word flags. - search = FPDFText_FindStart(textpage, world.get(), - FPDF_MATCHCASE | FPDF_MATCHWHOLEWORD, 0); - EXPECT_TRUE(search); - EXPECT_TRUE(FPDFText_FindNext(search)); - EXPECT_EQ(7, FPDFText_GetSchResultIndex(search)); - EXPECT_EQ(5, FPDFText_GetSchCount(search)); - FPDFText_FindClose(search); - - // Default is case-insensitive, so matching agaist caps works. - search = FPDFText_FindStart(textpage, world_caps.get(), 0, 0); - EXPECT_TRUE(search); - EXPECT_TRUE(FPDFText_FindNext(search)); - EXPECT_EQ(7, FPDFText_GetSchResultIndex(search)); - EXPECT_EQ(5, FPDFText_GetSchCount(search)); - FPDFText_FindClose(search); - - // But can be made case sensitive, in which case this fails. - search = FPDFText_FindStart(textpage, world_caps.get(), FPDF_MATCHCASE, 0); - EXPECT_FALSE(FPDFText_FindNext(search)); - EXPECT_EQ(0, FPDFText_GetSchResultIndex(search)); - EXPECT_EQ(0, FPDFText_GetSchCount(search)); - FPDFText_FindClose(search); - - // Default is match anywhere within word, so matching substirng works. - search = FPDFText_FindStart(textpage, world_substr.get(), 0, 0); - EXPECT_TRUE(FPDFText_FindNext(search)); - EXPECT_EQ(8, FPDFText_GetSchResultIndex(search)); - EXPECT_EQ(4, FPDFText_GetSchCount(search)); - FPDFText_FindClose(search); - - // But can be made to mach word boundaries, in which case this fails. - search = - FPDFText_FindStart(textpage, world_substr.get(), FPDF_MATCHWHOLEWORD, 0); - EXPECT_FALSE(FPDFText_FindNext(search)); - // TODO(tsepez): investigate strange index/count values in this state. - FPDFText_FindClose(search); - - FPDFText_ClosePage(textpage); - UnloadPage(page); -} - -// Test that the page has characters despite a bad stream length. -TEST_F(FPDFTextEmbeddertest, StreamLengthPastEndOfFile) { - ASSERT_TRUE(OpenDocument("bug_57.pdf")); - FPDF_PAGE page = LoadPage(0); - ASSERT_TRUE(page); - - FPDF_TEXTPAGE textpage = FPDFText_LoadPage(page); - ASSERT_TRUE(textpage); - EXPECT_EQ(13, FPDFText_CountChars(textpage)); - - FPDFText_ClosePage(textpage); - UnloadPage(page); -} - -TEST_F(FPDFTextEmbeddertest, WebLinks) { - ASSERT_TRUE(OpenDocument("weblinks.pdf")); - FPDF_PAGE page = LoadPage(0); - ASSERT_TRUE(page); - - FPDF_TEXTPAGE textpage = FPDFText_LoadPage(page); - ASSERT_TRUE(textpage); - - FPDF_PAGELINK pagelink = FPDFLink_LoadWebLinks(textpage); - EXPECT_TRUE(pagelink); - - // Page contains two HTTP-style URLs. - EXPECT_EQ(2, FPDFLink_CountWebLinks(pagelink)); - - // Only a terminating NUL required for bogus links. - EXPECT_EQ(1, FPDFLink_GetURL(pagelink, 2, nullptr, 0)); - EXPECT_EQ(1, FPDFLink_GetURL(pagelink, 1400, nullptr, 0)); - EXPECT_EQ(1, FPDFLink_GetURL(pagelink, -1, nullptr, 0)); - - // Query the number of characters required for each link (incl NUL). - EXPECT_EQ(25, FPDFLink_GetURL(pagelink, 0, nullptr, 0)); - EXPECT_EQ(26, FPDFLink_GetURL(pagelink, 1, nullptr, 0)); - - static const char expected_url[] = "http://example.com?q=foo"; - static const size_t expected_len = sizeof(expected_url); - unsigned short buffer[128]; - - // Retrieve a link with too small a buffer. Buffer will not be - // NUL-terminated, but must not be modified past indicated length, - // so pre-fill with a pattern to check write bounds. - memset(buffer, 0xbd, sizeof(buffer)); - EXPECT_EQ(1, FPDFLink_GetURL(pagelink, 0, buffer, 1)); - EXPECT_TRUE(check_unsigned_shorts(expected_url, buffer, 1)); - EXPECT_EQ(0xbdbd, buffer[1]); - - // Check buffer that doesn't have space for a terminating NUL. - memset(buffer, 0xbd, sizeof(buffer)); - EXPECT_EQ(static_cast(expected_len - 1), - FPDFLink_GetURL(pagelink, 0, buffer, expected_len - 1)); - EXPECT_TRUE(check_unsigned_shorts(expected_url, buffer, expected_len - 1)); - EXPECT_EQ(0xbdbd, buffer[expected_len - 1]); - - // Retreive link with exactly-sized buffer. - memset(buffer, 0xbd, sizeof(buffer)); - EXPECT_EQ(static_cast(expected_len), - FPDFLink_GetURL(pagelink, 0, buffer, expected_len)); - EXPECT_TRUE(check_unsigned_shorts(expected_url, buffer, expected_len)); - EXPECT_EQ(0u, buffer[expected_len - 1]); - EXPECT_EQ(0xbdbd, buffer[expected_len]); - - // Retreive link with ample-sized-buffer. - memset(buffer, 0xbd, sizeof(buffer)); - EXPECT_EQ(static_cast(expected_len), - FPDFLink_GetURL(pagelink, 0, buffer, 128)); - EXPECT_TRUE(check_unsigned_shorts(expected_url, buffer, expected_len)); - EXPECT_EQ(0u, buffer[expected_len - 1]); - EXPECT_EQ(0xbdbd, buffer[expected_len]); - - // Each link rendered in a single rect in this test page. - EXPECT_EQ(1, FPDFLink_CountRects(pagelink, 0)); - EXPECT_EQ(1, FPDFLink_CountRects(pagelink, 1)); - - // Each link rendered in a single rect in this test page. - EXPECT_EQ(0, FPDFLink_CountRects(pagelink, -1)); - EXPECT_EQ(0, FPDFLink_CountRects(pagelink, 2)); - EXPECT_EQ(0, FPDFLink_CountRects(pagelink, 10000)); - - // Check boundary of valid link index with valid rect index. - double left = 0.0; - double right = 0.0; - double top = 0.0; - double bottom = 0.0; - EXPECT_TRUE(FPDFLink_GetRect(pagelink, 0, 0, &left, &top, &right, &bottom)); - EXPECT_NEAR(50.791, left, 0.001); - EXPECT_NEAR(187.963, right, 0.001); - EXPECT_NEAR(97.624, bottom, 0.001); - EXPECT_NEAR(108.736, top, 0.001); - - // Check that valid link with invalid rect index leaves parameters unchanged. - left = -1.0; - right = -1.0; - top = -1.0; - bottom = -1.0; - EXPECT_FALSE(FPDFLink_GetRect(pagelink, 0, 1, &left, &top, &right, &bottom)); - EXPECT_EQ(-1.0, left); - EXPECT_EQ(-1.0, right); - EXPECT_EQ(-1.0, bottom); - EXPECT_EQ(-1.0, top); - - // Check that invalid link index leaves parameters unchanged. - left = -2.0; - right = -2.0; - top = -2.0; - bottom = -2.0; - EXPECT_FALSE(FPDFLink_GetRect(pagelink, -1, 0, &left, &top, &right, &bottom)); - EXPECT_EQ(-2.0, left); - EXPECT_EQ(-2.0, right); - EXPECT_EQ(-2.0, bottom); - EXPECT_EQ(-2.0, top); - - FPDFLink_CloseWebLinks(pagelink); - FPDFText_ClosePage(textpage); - UnloadPage(page); -} - -TEST_F(FPDFTextEmbeddertest, WebLinksAcrossLines) { - ASSERT_TRUE(OpenDocument("weblinks_across_lines.pdf")); - FPDF_PAGE page = LoadPage(0); - ASSERT_TRUE(page); - - FPDF_TEXTPAGE textpage = FPDFText_LoadPage(page); - ASSERT_TRUE(textpage); - - FPDF_PAGELINK pagelink = FPDFLink_LoadWebLinks(textpage); - EXPECT_TRUE(pagelink); - - static const char* const kExpectedUrls[] = { - "http://example.com", // from "http://www.example.com?\r\nfoo" - "http://example.com/", // from "http://www.example.com/\r\nfoo" - "http://example.com/test-foo", // from "http://example.com/test-\r\nfoo" - "http://abc.com/test-foo", // from "http://abc.com/test-\r\n\r\nfoo" - // Next two links from "http://www.example.com/\r\nhttp://www.abc.com/" - "http://example.com/", "http://www.abc.com", - }; - static const int kNumLinks = static_cast(FX_ArraySize(kExpectedUrls)); - - EXPECT_EQ(kNumLinks, FPDFLink_CountWebLinks(pagelink)); - - unsigned short buffer[128]; - for (int i = 0; i < kNumLinks; i++) { - const size_t expected_len = strlen(kExpectedUrls[i]) + 1; - memset(buffer, 0, FX_ArraySize(buffer)); - EXPECT_EQ(static_cast(expected_len), - FPDFLink_GetURL(pagelink, i, nullptr, 0)); - EXPECT_EQ(static_cast(expected_len), - FPDFLink_GetURL(pagelink, i, buffer, FX_ArraySize(buffer))); - EXPECT_TRUE(check_unsigned_shorts(kExpectedUrls[i], buffer, expected_len)); - } - - FPDFLink_CloseWebLinks(pagelink); - FPDFText_ClosePage(textpage); - UnloadPage(page); -} - -TEST_F(FPDFTextEmbeddertest, WebLinksAcrossLinesBug) { - ASSERT_TRUE(OpenDocument("bug_650.pdf")); - FPDF_PAGE page = LoadPage(0); - ASSERT_TRUE(page); - - FPDF_TEXTPAGE textpage = FPDFText_LoadPage(page); - ASSERT_TRUE(textpage); - - FPDF_PAGELINK pagelink = FPDFLink_LoadWebLinks(textpage); - EXPECT_TRUE(pagelink); - - EXPECT_EQ(2, FPDFLink_CountWebLinks(pagelink)); - unsigned short buffer[128] = {0}; - static const char kExpectedUrl[] = - "http://tutorial45.com/learn-autocad-basics-day-166/"; - static const int kUrlSize = static_cast(sizeof(kExpectedUrl)); - - EXPECT_EQ(kUrlSize, FPDFLink_GetURL(pagelink, 1, nullptr, 0)); - EXPECT_EQ(kUrlSize, - FPDFLink_GetURL(pagelink, 1, buffer, FX_ArraySize(buffer))); - EXPECT_TRUE(check_unsigned_shorts(kExpectedUrl, buffer, kUrlSize)); - - FPDFLink_CloseWebLinks(pagelink); - FPDFText_ClosePage(textpage); - UnloadPage(page); -} - -TEST_F(FPDFTextEmbeddertest, GetFontSize) { - ASSERT_TRUE(OpenDocument("hello_world.pdf")); - FPDF_PAGE page = LoadPage(0); - ASSERT_TRUE(page); - - FPDF_TEXTPAGE textpage = FPDFText_LoadPage(page); - ASSERT_TRUE(textpage); - - const double kExpectedFontsSizes[] = {12, 12, 12, 12, 12, 12, 12, 12, 12, 12, - 12, 12, 12, 1, 1, 16, 16, 16, 16, 16, - 16, 16, 16, 16, 16, 16, 16, 16, 16, 16}; - - int count = FPDFText_CountChars(textpage); - ASSERT_EQ(FX_ArraySize(kExpectedFontsSizes), static_cast(count)); - for (int i = 0; i < count; ++i) - EXPECT_EQ(kExpectedFontsSizes[i], FPDFText_GetFontSize(textpage, i)) << i; - - FPDFText_ClosePage(textpage); - UnloadPage(page); -} - -TEST_F(FPDFTextEmbeddertest, ToUnicode) { - ASSERT_TRUE(OpenDocument("bug_583.pdf")); - FPDF_PAGE page = LoadPage(0); - ASSERT_TRUE(page); - - FPDF_TEXTPAGE textpage = FPDFText_LoadPage(page); - ASSERT_TRUE(textpage); - - ASSERT_EQ(1, FPDFText_CountChars(textpage)); - EXPECT_EQ(static_cast(0), FPDFText_GetUnicode(textpage, 0)); - - FPDFText_ClosePage(textpage); - UnloadPage(page); -} - -TEST_F(FPDFTextEmbeddertest, Bug_921) { - ASSERT_TRUE(OpenDocument("bug_921.pdf")); - FPDF_PAGE page = LoadPage(0); - ASSERT_TRUE(page); - - FPDF_TEXTPAGE textpage = FPDFText_LoadPage(page); - ASSERT_TRUE(textpage); - - static constexpr unsigned int kData[] = { - 1095, 1077, 1083, 1086, 1074, 1077, 1095, 1077, 1089, 1082, 1086, 1077, - 32, 1089, 1090, 1088, 1072, 1076, 1072, 1085, 1080, 1077, 46, 32}; - static constexpr int kStartIndex = 238; - - ASSERT_EQ(268, FPDFText_CountChars(textpage)); - for (size_t i = 0; i < FX_ArraySize(kData); ++i) - EXPECT_EQ(kData[i], FPDFText_GetUnicode(textpage, kStartIndex + i)); - - unsigned short buffer[FX_ArraySize(kData) + 1]; - memset(buffer, 0xbd, sizeof(buffer)); - int count = - FPDFText_GetText(textpage, kStartIndex, FX_ArraySize(kData), buffer); - ASSERT_GT(count, 0); - ASSERT_EQ(FX_ArraySize(kData) + 1, static_cast(count)); - for (size_t i = 0; i < FX_ArraySize(kData); ++i) - EXPECT_EQ(kData[i], buffer[i]); - EXPECT_EQ(0, buffer[FX_ArraySize(kData)]); - - FPDFText_ClosePage(textpage); - UnloadPage(page); -} - -TEST_F(FPDFTextEmbeddertest, GetTextWithHyphen) { - ASSERT_TRUE(OpenDocument("bug_781804.pdf")); - FPDF_PAGE page = LoadPage(0); - ASSERT_TRUE(page); - - FPDF_TEXTPAGE textpage = FPDFText_LoadPage(page); - ASSERT_TRUE(textpage); - - // Check that soft hyphens are not included - // Expecting 'Veritaserum', except there is a \uFFFE where the hyphen was in - // the original text. This is a weird thing that Adobe does, which we - // replicate. - constexpr unsigned short soft_expected[] = { - 0x0056, 0x0065, 0x0072, 0x0069, 0x0074, 0x0061, 0xfffe, - 0x0073, 0x0065, 0x0072, 0x0075, 0x006D, 0x0000}; - { - constexpr int count = FX_ArraySize(soft_expected) - 1; - unsigned short buffer[FX_ArraySize(soft_expected)]; - memset(buffer, 0, sizeof(buffer)); - - EXPECT_EQ(count + 1, FPDFText_GetText(textpage, 0, count, buffer)); - for (int i = 0; i < count; i++) - EXPECT_EQ(soft_expected[i], buffer[i]); - } - - // Check that hard hyphens are included - { - // There isn't the \0 in the actual doc, but there is a \r\n, so need to - // add 1 to get aligned. - constexpr size_t offset = FX_ArraySize(soft_expected) + 1; - // Expecting 'User-\r\ngenerated', the - is a unicode character, so cannnot - // store in a char[]. - constexpr unsigned short hard_expected[] = { - 0x0055, 0x0073, 0x0065, 0x0072, 0x2010, 0x000d, 0x000a, 0x0067, 0x0065, - 0x006e, 0x0065, 0x0072, 0x0061, 0x0074, 0x0065, 0x0064, 0x0000}; - constexpr int count = FX_ArraySize(hard_expected) - 1; - unsigned short buffer[FX_ArraySize(hard_expected)]; - - EXPECT_EQ(count + 1, FPDFText_GetText(textpage, offset, count, buffer)); - for (int i = 0; i < count; i++) - EXPECT_EQ(hard_expected[i], buffer[i]); - } - - FPDFText_ClosePage(textpage); - UnloadPage(page); -} - -TEST_F(FPDFTextEmbeddertest, bug_782596) { - // If there is a regression in this test, it will only fail under ASAN - ASSERT_TRUE(OpenDocument("bug_782596.pdf")); - FPDF_PAGE page = LoadPage(0); - ASSERT_TRUE(page); - FPDF_TEXTPAGE textpage = FPDFText_LoadPage(page); - ASSERT_TRUE(textpage); - FPDFText_ClosePage(textpage); - UnloadPage(page); -} - -TEST_F(FPDFTextEmbeddertest, ControlCharacters) { - ASSERT_TRUE(OpenDocument("control_characters.pdf")); - FPDF_PAGE page = LoadPage(0); - ASSERT_TRUE(page); - - FPDF_TEXTPAGE textpage = FPDFText_LoadPage(page); - ASSERT_TRUE(textpage); - - // Should not include the control characters in the output - static const char expected[] = "Hello, world!\r\nGoodbye, world!"; - unsigned short buffer[128]; - memset(buffer, 0xbd, sizeof(buffer)); - int num_chars = FPDFText_GetText(textpage, 0, 128, buffer); - - ASSERT_GE(num_chars, 0); - EXPECT_EQ(sizeof(expected), static_cast(num_chars)); - EXPECT_TRUE(check_unsigned_shorts(expected, buffer, sizeof(expected))); - - // Attempting to get a chunk of text after the control characters - static const char expected_substring[] = "Goodbye, world!"; - // Offset is the length of 'Hello, world!\r\n' + 2 control characters in the - // original stream - static const int offset = 17; - memset(buffer, 0xbd, sizeof(buffer)); - num_chars = FPDFText_GetText(textpage, offset, 128, buffer); - - ASSERT_GE(num_chars, 0); - EXPECT_EQ(sizeof(expected_substring), static_cast(num_chars)); - EXPECT_TRUE(check_unsigned_shorts(expected_substring, buffer, - sizeof(expected_substring))); - - FPDFText_ClosePage(textpage); - UnloadPage(page); -} - -// Testing that hyphen makers (0x0002) are replacing hard hyphens when -// the word contains non-ASCII characters. -TEST_F(FPDFTextEmbeddertest, bug_1029) { - ASSERT_TRUE(OpenDocument("bug_1029.pdf")); - FPDF_PAGE page = LoadPage(0); - ASSERT_TRUE(page); - - FPDF_TEXTPAGE textpage = FPDFText_LoadPage(page); - ASSERT_TRUE(textpage); - - constexpr int page_range_offset = 171; - constexpr int page_range_length = 56; - - // This text is: - // 'METADATA table. When the split has committed, it noti' followed - // by a 'soft hyphen' (0x0002) and then 'fi'. - // - // The original text has a fi ligature, but that is broken up into - // two characters when the PDF is processed. - constexpr unsigned int expected[] = { - 0x004d, 0x0045, 0x0054, 0x0041, 0x0044, 0x0041, 0x0054, 0x0041, - 0x0020, 0x0074, 0x0061, 0x0062, 0x006c, 0x0065, 0x002e, 0x0020, - 0x0057, 0x0068, 0x0065, 0x006e, 0x0020, 0x0074, 0x0068, 0x0065, - 0x0020, 0x0073, 0x0070, 0x006c, 0x0069, 0x0074, 0x0020, 0x0068, - 0x0061, 0x0073, 0x0020, 0x0063, 0x006f, 0x006d, 0x006d, 0x0069, - 0x0074, 0x0074, 0x0065, 0x0064, 0x002c, 0x0020, 0x0069, 0x0074, - 0x0020, 0x006e, 0x006f, 0x0074, 0x0069, 0x0002, 0x0066, 0x0069}; - static_assert(page_range_length == FX_ArraySize(expected), - "Expected should be the same size as the range being " - "extracted from page."); - EXPECT_LT(page_range_offset + page_range_length, - FPDFText_CountChars(textpage)); - - for (int i = 0; i < page_range_length; ++i) { - EXPECT_EQ(expected[i], - FPDFText_GetUnicode(textpage, page_range_offset + i)); - } - - FPDFText_ClosePage(textpage); - UnloadPage(page); -} - -TEST_F(FPDFTextEmbeddertest, CountRects) { - ASSERT_TRUE(OpenDocument("hello_world.pdf")); - FPDF_PAGE page = LoadPage(0); - ASSERT_TRUE(page); - - FPDF_TEXTPAGE textpage = FPDFText_LoadPage(page); - ASSERT_TRUE(textpage); - - // Sanity check hello_world.pdf. - // |num_chars| check includes the terminating NUL that is provided. - static const char kExpected[] = "Hello, world!\r\nGoodbye, world!"; - { - unsigned short buffer[128]; - int num_chars = FPDFText_GetText(textpage, 0, 128, buffer); - ASSERT_GE(num_chars, 0); - EXPECT_EQ(sizeof(kExpected), static_cast(num_chars)); - EXPECT_TRUE(check_unsigned_shorts(kExpected, buffer, sizeof(kExpected))); - } - - // Now test FPDFText_CountRects(). - static const int kHelloWorldEnd = strlen("Hello, world!"); - static const int kGoodbyeWorldStart = kHelloWorldEnd + 2; // "\r\n" - for (int start = 0; start < kHelloWorldEnd; ++start) { - // Always grab some part of "hello world" and some part of "goodbye world" - // Since -1 means "all". - EXPECT_EQ(2, FPDFText_CountRects(textpage, start, -1)); - - // No characters always means 0 rects. - EXPECT_EQ(0, FPDFText_CountRects(textpage, start, 0)); - - // 1 character stays within "hello world" - EXPECT_EQ(1, FPDFText_CountRects(textpage, start, 1)); - - // When |start| is 0, Having |kGoodbyeWorldStart| char count does not reach - // "goodbye world". - int expected_value = start ? 2 : 1; - EXPECT_EQ(expected_value, - FPDFText_CountRects(textpage, start, kGoodbyeWorldStart)); - - // Extremely large character count will always return 2 rects because - // |start| starts inside "hello world". - EXPECT_EQ(2, FPDFText_CountRects(textpage, start, 500)); - } - - // Now test negative counts. - for (int start = 0; start < kHelloWorldEnd; ++start) { - EXPECT_EQ(2, FPDFText_CountRects(textpage, start, -100)); - EXPECT_EQ(2, FPDFText_CountRects(textpage, start, -2)); - } - - // Now test larger start values. - const int kExpectedLength = strlen(kExpected); - for (int start = kGoodbyeWorldStart + 1; start < kExpectedLength; ++start) { - EXPECT_EQ(1, FPDFText_CountRects(textpage, start, -1)); - EXPECT_EQ(0, FPDFText_CountRects(textpage, start, 0)); - EXPECT_EQ(1, FPDFText_CountRects(textpage, start, 1)); - EXPECT_EQ(1, FPDFText_CountRects(textpage, start, 2)); - EXPECT_EQ(1, FPDFText_CountRects(textpage, start, 500)); - } - - // TODO(thestig): This crashes. Fix and enable. - // Now test start values that starts beyond the end of the text. - for (int start = kExpectedLength; start < 100; ++start) { - EXPECT_EQ(0, FPDFText_CountRects(textpage, start, -1)); - EXPECT_EQ(0, FPDFText_CountRects(textpage, start, 0)); - EXPECT_EQ(0, FPDFText_CountRects(textpage, start, 1)); - EXPECT_EQ(0, FPDFText_CountRects(textpage, start, 2)); - EXPECT_EQ(0, FPDFText_CountRects(textpage, start, 500)); - } - - FPDFText_ClosePage(textpage); - UnloadPage(page); -} diff --git a/fpdfsdk/fpdfview.cpp b/fpdfsdk/fpdfview.cpp deleted file mode 100644 index 187db9b126..0000000000 --- a/fpdfsdk/fpdfview.cpp +++ /dev/null @@ -1,1158 +0,0 @@ -// Copyright 2014 PDFium Authors. All rights reserved. -// 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/fpdfview.h" - -#include -#include - -#include "core/fpdfapi/cpdf_modulemgr.h" -#include "core/fpdfapi/cpdf_pagerendercontext.h" -#include "core/fpdfapi/page/cpdf_page.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_name.h" -#include "core/fpdfapi/parser/cpdf_parser.h" -#include "core/fpdfapi/parser/fpdf_parser_decode.h" -#include "core/fpdfapi/render/cpdf_progressiverenderer.h" -#include "core/fpdfapi/render/cpdf_rendercontext.h" -#include "core/fpdfapi/render/cpdf_renderoptions.h" -#include "core/fpdfdoc/cpdf_annotlist.h" -#include "core/fpdfdoc/cpdf_nametree.h" -#include "core/fpdfdoc/cpdf_occontext.h" -#include "core/fpdfdoc/cpdf_viewerpreferences.h" -#include "core/fxcrt/fx_stream.h" -#include "core/fxcrt/fx_system.h" -#include "core/fxge/cfx_defaultrenderdevice.h" -#include "core/fxge/cfx_gemodule.h" -#include "core/fxge/cfx_renderdevice.h" -#include "fpdfsdk/cpdfsdk_customaccess.h" -#include "fpdfsdk/cpdfsdk_formfillenvironment.h" -#include "fpdfsdk/cpdfsdk_helpers.h" -#include "fpdfsdk/cpdfsdk_memoryaccess.h" -#include "fpdfsdk/cpdfsdk_pageview.h" -#include "fpdfsdk/ipdfsdk_pauseadapter.h" -#include "fxjs/ijs_runtime.h" -#include "public/fpdf_formfill.h" -#include "third_party/base/ptr_util.h" - -#ifdef PDF_ENABLE_XFA -#include "fpdfsdk/fpdfxfa/cpdfxfa_context.h" -#include "fpdfsdk/fpdfxfa/cpdfxfa_page.h" -#include "fxbarcode/BC_Library.h" -#endif // PDF_ENABLE_XFA - -#if _FX_PLATFORM_ == _FX_PLATFORM_WINDOWS_ -#include "core/fxge/cfx_windowsrenderdevice.h" -#include "public/fpdf_edit.h" - -// These checks are here because core/ and public/ cannot depend on each other. -static_assert(WindowsPrintMode::kModeEmf == FPDF_PRINTMODE_EMF, - "WindowsPrintMode::kModeEmf value mismatch"); -static_assert(WindowsPrintMode::kModeTextOnly == FPDF_PRINTMODE_TEXTONLY, - "WindowsPrintMode::kModeTextOnly value mismatch"); -static_assert(WindowsPrintMode::kModePostScript2 == FPDF_PRINTMODE_POSTSCRIPT2, - "WindowsPrintMode::kModePostScript2 value mismatch"); -static_assert(WindowsPrintMode::kModePostScript3 == FPDF_PRINTMODE_POSTSCRIPT3, - "WindowsPrintMode::kModePostScript3 value mismatch"); -#endif - -namespace { - -bool g_bLibraryInitialized = false; - -void RenderPageImpl(CPDF_PageRenderContext* pContext, - CPDF_Page* pPage, - const CFX_Matrix& matrix, - const FX_RECT& clipping_rect, - int flags, - bool bNeedToRestore, - IPDFSDK_PauseAdapter* pause) { - if (!pContext->m_pOptions) - pContext->m_pOptions = pdfium::MakeUnique(); - - uint32_t option_flags = pContext->m_pOptions->GetFlags(); - if (flags & FPDF_LCD_TEXT) - option_flags |= RENDER_CLEARTYPE; - else - option_flags &= ~RENDER_CLEARTYPE; - - if (flags & FPDF_NO_NATIVETEXT) - option_flags |= RENDER_NO_NATIVETEXT; - if (flags & FPDF_RENDER_LIMITEDIMAGECACHE) - option_flags |= RENDER_LIMITEDIMAGECACHE; - if (flags & FPDF_RENDER_FORCEHALFTONE) - option_flags |= RENDER_FORCE_HALFTONE; -#ifndef PDF_ENABLE_XFA - if (flags & FPDF_RENDER_NO_SMOOTHTEXT) - option_flags |= RENDER_NOTEXTSMOOTH; - if (flags & FPDF_RENDER_NO_SMOOTHIMAGE) - option_flags |= RENDER_NOIMAGESMOOTH; - if (flags & FPDF_RENDER_NO_SMOOTHPATH) - option_flags |= RENDER_NOPATHSMOOTH; -#endif // PDF_ENABLE_XFA - pContext->m_pOptions->SetFlags(option_flags); - - // Grayscale output - if (flags & FPDF_GRAYSCALE) - pContext->m_pOptions->SetColorMode(CPDF_RenderOptions::kGray); - - const CPDF_OCContext::UsageType usage = - (flags & FPDF_PRINTING) ? CPDF_OCContext::Print : CPDF_OCContext::View; - pContext->m_pOptions->SetOCContext( - pdfium::MakeRetain(pPage->m_pDocument.Get(), usage)); - - pContext->m_pDevice->SaveState(); - pContext->m_pDevice->SetClip_Rect(clipping_rect); - pContext->m_pContext = pdfium::MakeUnique(pPage); - pContext->m_pContext->AppendLayer(pPage, &matrix); - - if (flags & FPDF_ANNOT) { - pContext->m_pAnnots = pdfium::MakeUnique(pPage); - bool bPrinting = pContext->m_pDevice->GetDeviceClass() != FXDC_DISPLAY; - pContext->m_pAnnots->DisplayAnnots(pPage, pContext->m_pContext.get(), - bPrinting, &matrix, false, nullptr); - } - - pContext->m_pRenderer = pdfium::MakeUnique( - pContext->m_pContext.get(), pContext->m_pDevice.get(), - pContext->m_pOptions.get()); - pContext->m_pRenderer->Start(pause); - if (bNeedToRestore) - pContext->m_pDevice->RestoreState(false); -} - -FPDF_DOCUMENT LoadDocumentImpl( - const RetainPtr& pFileAccess, - FPDF_BYTESTRING password) { - if (!pFileAccess) { - ProcessParseError(CPDF_Parser::FILE_ERROR); - return nullptr; - } - - auto pParser = pdfium::MakeUnique(); - pParser->SetPassword(password); - - auto pDocument = pdfium::MakeUnique(std::move(pParser)); - CPDF_Parser::Error error = - pDocument->GetParser()->StartParse(pFileAccess, pDocument.get()); - if (error != CPDF_Parser::SUCCESS) { - ProcessParseError(error); - return nullptr; - } - CheckUnSupportError(pDocument.get(), error); - return FPDFDocumentFromCPDFDocument(pDocument.release()); -} - -} // namespace - -FPDF_EXPORT void FPDF_CALLCONV FPDF_InitLibrary() { - FPDF_InitLibraryWithConfig(nullptr); -} - -FPDF_EXPORT void FPDF_CALLCONV -FPDF_InitLibraryWithConfig(const FPDF_LIBRARY_CONFIG* cfg) { - if (g_bLibraryInitialized) - return; - - FXMEM_InitializePartitionAlloc(); - - CFX_GEModule* pModule = CFX_GEModule::Get(); - pModule->Init(cfg ? cfg->m_pUserFontPaths : nullptr); - - CPDF_ModuleMgr* pModuleMgr = CPDF_ModuleMgr::Get(); - pModuleMgr->Init(); - -#ifdef PDF_ENABLE_XFA - BC_Library_Init(); -#endif // PDF_ENABLE_XFA - if (cfg && cfg->version >= 2) - IJS_Runtime::Initialize(cfg->m_v8EmbedderSlot, cfg->m_pIsolate); - - g_bLibraryInitialized = true; -} - -FPDF_EXPORT void FPDF_CALLCONV FPDF_DestroyLibrary() { - if (!g_bLibraryInitialized) - return; - -#ifdef PDF_ENABLE_XFA - BC_Library_Destroy(); -#endif // PDF_ENABLE_XFA - - CPDF_ModuleMgr::Destroy(); - CFX_GEModule::Destroy(); - - IJS_Runtime::Destroy(); - - g_bLibraryInitialized = false; -} - -FPDF_EXPORT void FPDF_CALLCONV FPDF_SetSandBoxPolicy(FPDF_DWORD policy, - FPDF_BOOL enable) { - return FSDK_SetSandBoxPolicy(policy, enable); -} - -#if defined(_WIN32) -#if defined(PDFIUM_PRINT_TEXT_WITH_GDI) -FPDF_EXPORT void FPDF_CALLCONV -FPDF_SetTypefaceAccessibleFunc(PDFiumEnsureTypefaceCharactersAccessible func) { - g_pdfium_typeface_accessible_func = func; -} - -FPDF_EXPORT void FPDF_CALLCONV FPDF_SetPrintTextWithGDI(FPDF_BOOL use_gdi) { - g_pdfium_print_text_with_gdi = !!use_gdi; -} -#endif // PDFIUM_PRINT_TEXT_WITH_GDI - -FPDF_EXPORT FPDF_BOOL FPDF_CALLCONV -FPDF_SetPrintPostscriptLevel(int postscript_level) { - return postscript_level != 1 && FPDF_SetPrintMode(postscript_level); -} - -FPDF_EXPORT FPDF_BOOL FPDF_CALLCONV FPDF_SetPrintMode(int mode) { - if (mode < FPDF_PRINTMODE_EMF || mode > FPDF_PRINTMODE_POSTSCRIPT3) - return FALSE; - g_pdfium_print_mode = mode; - return TRUE; -} -#endif // defined(_WIN32) - -FPDF_EXPORT FPDF_DOCUMENT FPDF_CALLCONV -FPDF_LoadDocument(FPDF_STRING file_path, FPDF_BYTESTRING password) { - // NOTE: the creation of the file needs to be by the embedder on the - // other side of this API. - return LoadDocumentImpl(IFX_SeekableReadStream::CreateFromFilename(file_path), - password); -} - -FPDF_EXPORT int FPDF_CALLCONV FPDF_GetFormType(FPDF_DOCUMENT document) { - const CPDF_Document* pDoc = CPDFDocumentFromFPDFDocument(document); - if (!pDoc) - return FORMTYPE_NONE; - - const CPDF_Dictionary* pRoot = pDoc->GetRoot(); - if (!pRoot) - return FORMTYPE_NONE; - - CPDF_Dictionary* pAcroForm = pRoot->GetDictFor("AcroForm"); - if (!pAcroForm) - return FORMTYPE_NONE; - - CPDF_Object* pXFA = pAcroForm->GetObjectFor("XFA"); - if (!pXFA) - return FORMTYPE_ACRO_FORM; - - bool bNeedsRendering = pRoot->GetBooleanFor("NeedsRendering", false); - return bNeedsRendering ? FORMTYPE_XFA_FULL : FORMTYPE_XFA_FOREGROUND; -} - -#ifdef PDF_ENABLE_XFA -FPDF_EXPORT FPDF_BOOL FPDF_CALLCONV FPDF_LoadXFA(FPDF_DOCUMENT document) { - return document && static_cast(document)->LoadXFADoc(); -} -#endif // PDF_ENABLE_XFA - -FPDF_EXPORT FPDF_DOCUMENT FPDF_CALLCONV -FPDF_LoadMemDocument(const void* data_buf, int size, FPDF_BYTESTRING password) { - return LoadDocumentImpl(pdfium::MakeRetain( - static_cast(data_buf), size), - password); -} - -FPDF_EXPORT FPDF_DOCUMENT FPDF_CALLCONV -FPDF_LoadCustomDocument(FPDF_FILEACCESS* pFileAccess, - FPDF_BYTESTRING password) { - return LoadDocumentImpl(pdfium::MakeRetain(pFileAccess), - password); -} - -FPDF_EXPORT FPDF_BOOL FPDF_CALLCONV FPDF_GetFileVersion(FPDF_DOCUMENT doc, - int* fileVersion) { - if (!fileVersion) - return false; - - *fileVersion = 0; - CPDF_Document* pDoc = CPDFDocumentFromFPDFDocument(doc); - if (!pDoc) - return false; - - const CPDF_Parser* pParser = pDoc->GetParser(); - if (!pParser) - return false; - - *fileVersion = pParser->GetFileVersion(); - return true; -} - -// jabdelmalek: changed return type from uint32_t to build on Linux (and match -// header). -FPDF_EXPORT unsigned long FPDF_CALLCONV -FPDF_GetDocPermissions(FPDF_DOCUMENT document) { - CPDF_Document* pDoc = CPDFDocumentFromFPDFDocument(document); - // https://bugs.chromium.org/p/pdfium/issues/detail?id=499 - if (!pDoc) { -#ifndef PDF_ENABLE_XFA - return 0; -#else // PDF_ENABLE_XFA - return 0xFFFFFFFF; -#endif // PDF_ENABLE_XFA - } - - return pDoc->GetUserPermissions(); -} - -FPDF_EXPORT int FPDF_CALLCONV -FPDF_GetSecurityHandlerRevision(FPDF_DOCUMENT document) { - CPDF_Document* pDoc = CPDFDocumentFromFPDFDocument(document); - if (!pDoc || !pDoc->GetParser()) - return -1; - - CPDF_Dictionary* pDict = pDoc->GetParser()->GetEncryptDict(); - return pDict ? pDict->GetIntegerFor("R") : -1; -} - -FPDF_EXPORT int FPDF_CALLCONV FPDF_GetPageCount(FPDF_DOCUMENT document) { - UnderlyingDocumentType* pDoc = UnderlyingFromFPDFDocument(document); - return pDoc ? pDoc->GetPageCount() : 0; -} - -FPDF_EXPORT FPDF_PAGE FPDF_CALLCONV FPDF_LoadPage(FPDF_DOCUMENT document, - int page_index) { - UnderlyingDocumentType* pDoc = UnderlyingFromFPDFDocument(document); - if (!pDoc) - return nullptr; - - if (page_index < 0 || page_index >= pDoc->GetPageCount()) - return nullptr; - -#ifdef PDF_ENABLE_XFA - return pDoc->GetXFAPage(page_index).Leak(); -#else // PDF_ENABLE_XFA - CPDF_Dictionary* pDict = pDoc->GetPage(page_index); - if (!pDict) - return nullptr; - - CPDF_Page* pPage = new CPDF_Page(pDoc, pDict, true); - pPage->ParseContent(); - return pPage; -#endif // PDF_ENABLE_XFA -} - -FPDF_EXPORT double FPDF_CALLCONV FPDF_GetPageWidth(FPDF_PAGE page) { - UnderlyingPageType* pPage = UnderlyingFromFPDFPage(page); - return pPage ? pPage->GetPageWidth() : 0.0; -} - -FPDF_EXPORT double FPDF_CALLCONV FPDF_GetPageHeight(FPDF_PAGE page) { - UnderlyingPageType* pPage = UnderlyingFromFPDFPage(page); - return pPage ? pPage->GetPageHeight() : 0.0; -} - -FPDF_EXPORT FPDF_BOOL FPDF_CALLCONV FPDF_GetPageBoundingBox(FPDF_PAGE page, - FS_RECTF* rect) { - if (!rect) - return false; - - CPDF_Page* pPage = CPDFPageFromFPDFPage(page); - if (!pPage) - return false; - - FSRECTFFromCFXFloatRect(pPage->GetPageBBox(), rect); - return true; -} - -#if defined(_WIN32) -namespace { - -const double kEpsilonSize = 0.01f; - -void GetScaling(CPDF_Page* pPage, - int size_x, - int size_y, - int rotate, - double* scale_x, - double* scale_y) { - ASSERT(pPage); - ASSERT(scale_x); - ASSERT(scale_y); - double page_width = pPage->GetPageWidth(); - double page_height = pPage->GetPageHeight(); - if (page_width < kEpsilonSize || page_height < kEpsilonSize) - return; - - if (rotate % 2 == 0) { - *scale_x = size_x / page_width; - *scale_y = size_y / page_height; - } else { - *scale_x = size_y / page_width; - *scale_y = size_x / page_height; - } -} - -FX_RECT GetMaskDimensionsAndOffsets(CPDF_Page* pPage, - int start_x, - int start_y, - int size_x, - int size_y, - int rotate, - const CFX_FloatRect& mask_box) { - double scale_x = 0.0f; - double scale_y = 0.0f; - GetScaling(pPage, size_x, size_y, rotate, &scale_x, &scale_y); - if (scale_x < kEpsilonSize || scale_y < kEpsilonSize) - return FX_RECT(); - - // Compute sizes in page points. Round down to catch the entire bitmap. - int start_x_bm = static_cast(mask_box.left * scale_x); - int start_y_bm = static_cast(mask_box.bottom * scale_y); - int size_x_bm = static_cast(mask_box.right * scale_x + 1.0f) - - static_cast(mask_box.left * scale_x); - int size_y_bm = static_cast(mask_box.top * scale_y + 1.0f) - - static_cast(mask_box.bottom * scale_y); - - // Get page rotation - int page_rotation = pPage->GetPageRotation(); - - // Compute offsets - int offset_x = 0; - int offset_y = 0; - if (size_x > size_y) - std::swap(size_x_bm, size_y_bm); - - switch ((rotate + page_rotation) % 4) { - case 0: - offset_x = start_x_bm + start_x; - offset_y = start_y + size_y - size_y_bm - start_y_bm; - break; - case 1: - offset_x = start_y_bm + start_x; - offset_y = start_x_bm + start_y; - break; - case 2: - offset_x = start_x + size_x - size_x_bm - start_x_bm; - offset_y = start_y_bm + start_y; - break; - case 3: - offset_x = start_x + size_x - size_x_bm - start_y_bm; - offset_y = start_y + size_y - size_y_bm - start_x_bm; - break; - } - return FX_RECT(offset_x, offset_y, offset_x + size_x_bm, - offset_y + size_y_bm); -} - -// Get a bitmap of just the mask section defined by |mask_box| from a full page -// bitmap |pBitmap|. -RetainPtr GetMaskBitmap(CPDF_Page* pPage, - int start_x, - int start_y, - int size_x, - int size_y, - int rotate, - const RetainPtr& pSrc, - const CFX_FloatRect& mask_box, - FX_RECT* bitmap_area) { - ASSERT(bitmap_area); - *bitmap_area = GetMaskDimensionsAndOffsets(pPage, start_x, start_y, size_x, - size_y, rotate, mask_box); - if (bitmap_area->IsEmpty()) - return nullptr; - - // Create a new bitmap to transfer part of the page bitmap to. - RetainPtr pDst = pdfium::MakeRetain(); - pDst->Create(bitmap_area->Width(), bitmap_area->Height(), FXDIB_Argb); - pDst->Clear(0x00ffffff); - pDst->TransferBitmap(0, 0, bitmap_area->Width(), bitmap_area->Height(), pSrc, - bitmap_area->left, bitmap_area->top); - return pDst; -} - -void RenderBitmap(CFX_RenderDevice* device, - const RetainPtr& pSrc, - const FX_RECT& mask_area) { - int size_x_bm = mask_area.Width(); - int size_y_bm = mask_area.Height(); - if (size_x_bm == 0 || size_y_bm == 0) - return; - - // Create a new bitmap from the old one - RetainPtr pDst = pdfium::MakeRetain(); - pDst->Create(size_x_bm, size_y_bm, FXDIB_Rgb32); - pDst->Clear(0xffffffff); - pDst->CompositeBitmap(0, 0, size_x_bm, size_y_bm, pSrc, 0, 0, - FXDIB_BLEND_NORMAL, nullptr, false); - - if (device->GetDeviceCaps(FXDC_DEVICE_CLASS) == FXDC_PRINTER) { - device->StretchDIBits(pDst, mask_area.left, mask_area.top, size_x_bm, - size_y_bm); - } else { - device->SetDIBits(pDst, mask_area.left, mask_area.top); - } -} - -} // namespace - -FPDF_EXPORT void FPDF_CALLCONV FPDF_RenderPage(HDC dc, - FPDF_PAGE page, - int start_x, - int start_y, - int size_x, - int size_y, - int rotate, - int flags) { - CPDF_Page* pPage = CPDFPageFromFPDFPage(page); - if (!pPage) - return; - pPage->SetRenderContext(pdfium::MakeUnique()); - CPDF_PageRenderContext* pContext = pPage->GetRenderContext(); - - RetainPtr pBitmap; - // Don't render the full page to bitmap for a mask unless there are a lot - // of masks. Full page bitmaps result in large spool sizes, so they should - // only be used when necessary. For large numbers of masks, rendering each - // individually is inefficient and unlikely to significantly improve spool - // size. TODO (rbpotter): Find out why this still breaks printing for some - // PDFs (see crbug.com/777837). - const bool bEnableImageMasks = false; - const bool bNewBitmap = pPage->BackgroundAlphaNeeded() || - (pPage->HasImageMask() && !bEnableImageMasks) || - pPage->GetMaskBoundingBoxes().size() > 100; - const bool bHasMask = pPage->HasImageMask() && !bNewBitmap; - if (bNewBitmap || bHasMask) { - pBitmap = pdfium::MakeRetain(); - pBitmap->Create(size_x, size_y, FXDIB_Argb); - pBitmap->Clear(0x00ffffff); - CFX_DefaultRenderDevice* pDevice = new CFX_DefaultRenderDevice; - pContext->m_pDevice = pdfium::WrapUnique(pDevice); - pDevice->Attach(pBitmap, false, nullptr, false); - if (bHasMask) { - pContext->m_pOptions = pdfium::MakeUnique(); - uint32_t option_flags = pContext->m_pOptions->GetFlags(); - option_flags |= RENDER_BREAKFORMASKS; - pContext->m_pOptions->SetFlags(option_flags); - } - } else { - pContext->m_pDevice = pdfium::MakeUnique(dc); - } - - FPDF_RenderPage_Retail(pContext, page, start_x, start_y, size_x, size_y, - rotate, flags, true, nullptr); - - if (bHasMask) { - // Finish rendering the page to bitmap and copy the correct segments - // of the page to individual image mask bitmaps. - const std::vector& mask_boxes = - pPage->GetMaskBoundingBoxes(); - std::vector bitmap_areas(mask_boxes.size()); - std::vector> bitmaps(mask_boxes.size()); - for (size_t i = 0; i < mask_boxes.size(); i++) { - bitmaps[i] = - GetMaskBitmap(pPage, start_x, start_y, size_x, size_y, rotate, - pBitmap, mask_boxes[i], &bitmap_areas[i]); - pContext->m_pRenderer->Continue(nullptr); - } - - // Reset rendering context - pPage->SetRenderContext(nullptr); - - // Begin rendering to the printer. Add flag to indicate the renderer should - // pause after each image mask. - pPage->SetRenderContext(pdfium::MakeUnique()); - pContext = pPage->GetRenderContext(); - pContext->m_pDevice = pdfium::MakeUnique(dc); - pContext->m_pOptions = pdfium::MakeUnique(); - - uint32_t option_flags = pContext->m_pOptions->GetFlags(); - option_flags |= RENDER_BREAKFORMASKS; - pContext->m_pOptions->SetFlags(option_flags); - - FPDF_RenderPage_Retail(pContext, page, start_x, start_y, size_x, size_y, - rotate, flags, true, nullptr); - - // Render masks - for (size_t i = 0; i < mask_boxes.size(); i++) { - // Render the bitmap for the mask and free the bitmap. - if (bitmaps[i]) { // will be null if mask has zero area - RenderBitmap(pContext->m_pDevice.get(), bitmaps[i], bitmap_areas[i]); - } - // Render the next portion of page. - pContext->m_pRenderer->Continue(nullptr); - } - } else if (bNewBitmap) { - CFX_WindowsRenderDevice WinDC(dc); - if (WinDC.GetDeviceCaps(FXDC_DEVICE_CLASS) == FXDC_PRINTER) { - auto pDst = pdfium::MakeRetain(); - int pitch = pBitmap->GetPitch(); - pDst->Create(size_x, size_y, FXDIB_Rgb32); - memset(pDst->GetBuffer(), -1, pitch * size_y); - pDst->CompositeBitmap(0, 0, size_x, size_y, pBitmap, 0, 0, - FXDIB_BLEND_NORMAL, nullptr, false); - WinDC.StretchDIBits(pDst, 0, 0, size_x, size_y); - } else { - WinDC.SetDIBits(pBitmap, 0, 0); - } - } - - pPage->SetRenderContext(nullptr); -} -#endif // defined(_WIN32) - -FPDF_EXPORT void FPDF_CALLCONV FPDF_RenderPageBitmap(FPDF_BITMAP bitmap, - FPDF_PAGE page, - int start_x, - int start_y, - int size_x, - int size_y, - int rotate, - int flags) { - if (!bitmap) - return; - - CPDF_Page* pPage = CPDFPageFromFPDFPage(page); - if (!pPage) - return; - - CPDF_PageRenderContext* pContext = new CPDF_PageRenderContext; - pPage->SetRenderContext(pdfium::WrapUnique(pContext)); - - CFX_DefaultRenderDevice* pDevice = new CFX_DefaultRenderDevice; - pContext->m_pDevice.reset(pDevice); - - RetainPtr pBitmap(CFXBitmapFromFPDFBitmap(bitmap)); - pDevice->Attach(pBitmap, !!(flags & FPDF_REVERSE_BYTE_ORDER), nullptr, false); - FPDF_RenderPage_Retail(pContext, page, start_x, start_y, size_x, size_y, - rotate, flags, true, nullptr); - -#ifdef _SKIA_SUPPORT_PATHS_ - pDevice->Flush(true); - pBitmap->UnPreMultiply(); -#endif - pPage->SetRenderContext(nullptr); -} - -FPDF_EXPORT void FPDF_CALLCONV -FPDF_RenderPageBitmapWithMatrix(FPDF_BITMAP bitmap, - FPDF_PAGE page, - const FS_MATRIX* matrix, - const FS_RECTF* clipping, - int flags) { - if (!bitmap) - return; - - CPDF_Page* pPage = CPDFPageFromFPDFPage(page); - if (!pPage) - return; - - CPDF_PageRenderContext* pContext = new CPDF_PageRenderContext; - pPage->SetRenderContext(pdfium::WrapUnique(pContext)); - - CFX_DefaultRenderDevice* pDevice = new CFX_DefaultRenderDevice; - pContext->m_pDevice.reset(pDevice); - - RetainPtr pBitmap(CFXBitmapFromFPDFBitmap(bitmap)); - pDevice->Attach(pBitmap, !!(flags & FPDF_REVERSE_BYTE_ORDER), nullptr, false); - - CFX_FloatRect clipping_rect; - if (clipping) - clipping_rect = CFXFloatRectFromFSRECTF(*clipping); - FX_RECT clip_rect = clipping_rect.ToFxRect(); - - CFX_Matrix transform_matrix = pPage->GetDisplayMatrix( - 0, 0, pPage->GetPageWidth(), pPage->GetPageHeight(), 0); - - if (matrix) { - transform_matrix.Concat(CFX_Matrix(matrix->a, matrix->b, matrix->c, - matrix->d, matrix->e, matrix->f)); - } - RenderPageImpl(pContext, pPage, transform_matrix, clip_rect, flags, true, - nullptr); - - pPage->SetRenderContext(nullptr); -} - -#ifdef _SKIA_SUPPORT_ -FPDF_EXPORT FPDF_RECORDER FPDF_CALLCONV FPDF_RenderPageSkp(FPDF_PAGE page, - int size_x, - int size_y) { - CPDF_Page* pPage = CPDFPageFromFPDFPage(page); - if (!pPage) - return nullptr; - - CPDF_PageRenderContext* pContext = new CPDF_PageRenderContext; - pPage->SetRenderContext(pdfium::WrapUnique(pContext)); - CFX_DefaultRenderDevice* skDevice = new CFX_DefaultRenderDevice; - FPDF_RECORDER recorder = skDevice->CreateRecorder(size_x, size_y); - pContext->m_pDevice.reset(skDevice); - FPDF_RenderPage_Retail(pContext, page, 0, 0, size_x, size_y, 0, 0, true, - nullptr); - pPage->SetRenderContext(nullptr); - return recorder; -} -#endif - -FPDF_EXPORT void FPDF_CALLCONV FPDF_ClosePage(FPDF_PAGE page) { - UnderlyingPageType* pPage = UnderlyingFromFPDFPage(page); - if (!page) - return; -#ifdef PDF_ENABLE_XFA - // Take it back across the API and throw it away. - RetainPtr().Unleak(pPage); -#else // PDF_ENABLE_XFA - CPDFSDK_PageView* pPageView = - static_cast(pPage->GetView()); - if (pPageView) { - // We're already destroying the pageview, so bail early. - if (pPageView->IsBeingDestroyed()) - return; - - if (pPageView->IsLocked()) { - pPageView->TakePageOwnership(); - return; - } - - bool owned = pPageView->OwnsPage(); - // This will delete the |pPageView| object. We must cleanup the PageView - // first because it will attempt to reset the View on the |pPage| during - // destruction. - pPageView->GetFormFillEnv()->RemovePageView(pPage); - // If the page was owned then the pageview will have deleted the page. - if (owned) - return; - } - delete pPage; -#endif // PDF_ENABLE_XFA -} - -FPDF_EXPORT void FPDF_CALLCONV FPDF_CloseDocument(FPDF_DOCUMENT document) { - delete UnderlyingFromFPDFDocument(document); -} - -FPDF_EXPORT unsigned long FPDF_CALLCONV FPDF_GetLastError() { - return GetLastError(); -} - -FPDF_EXPORT void FPDF_CALLCONV FPDF_DeviceToPage(FPDF_PAGE page, - int start_x, - int start_y, - int size_x, - int size_y, - int rotate, - int device_x, - int device_y, - double* page_x, - double* page_y) { - if (!page || !page_x || !page_y) - return; - UnderlyingPageType* pPage = UnderlyingFromFPDFPage(page); -#ifdef PDF_ENABLE_XFA - pPage->DeviceToPage(start_x, start_y, size_x, size_y, rotate, device_x, - device_y, page_x, page_y); -#else // PDF_ENABLE_XFA - CFX_Matrix page2device = - pPage->GetDisplayMatrix(start_x, start_y, size_x, size_y, rotate); - - CFX_PointF pos = page2device.GetInverse().Transform( - CFX_PointF(static_cast(device_x), static_cast(device_y))); - - *page_x = pos.x; - *page_y = pos.y; -#endif // PDF_ENABLE_XFA -} - -FPDF_EXPORT void FPDF_CALLCONV FPDF_PageToDevice(FPDF_PAGE page, - int start_x, - int start_y, - int size_x, - int size_y, - int rotate, - double page_x, - double page_y, - int* device_x, - int* device_y) { - if (!device_x || !device_y) - return; - UnderlyingPageType* pPage = UnderlyingFromFPDFPage(page); - if (!pPage) - return; -#ifdef PDF_ENABLE_XFA - pPage->PageToDevice(start_x, start_y, size_x, size_y, rotate, page_x, page_y, - device_x, device_y); -#else // PDF_ENABLE_XFA - CFX_Matrix page2device = - pPage->GetDisplayMatrix(start_x, start_y, size_x, size_y, rotate); - CFX_PointF pos = page2device.Transform( - CFX_PointF(static_cast(page_x), static_cast(page_y))); - - *device_x = FXSYS_round(pos.x); - *device_y = FXSYS_round(pos.y); -#endif // PDF_ENABLE_XFA -} - -FPDF_EXPORT FPDF_BITMAP FPDF_CALLCONV FPDFBitmap_Create(int width, - int height, - int alpha) { - auto pBitmap = pdfium::MakeRetain(); - if (!pBitmap->Create(width, height, alpha ? FXDIB_Argb : FXDIB_Rgb32)) - return nullptr; - - return pBitmap.Leak(); -} - -FPDF_EXPORT FPDF_BITMAP FPDF_CALLCONV FPDFBitmap_CreateEx(int width, - int height, - int format, - void* first_scan, - int stride) { - FXDIB_Format fx_format; - switch (format) { - case FPDFBitmap_Gray: - fx_format = FXDIB_8bppRgb; - break; - case FPDFBitmap_BGR: - fx_format = FXDIB_Rgb; - break; - case FPDFBitmap_BGRx: - fx_format = FXDIB_Rgb32; - break; - case FPDFBitmap_BGRA: - fx_format = FXDIB_Argb; - break; - default: - return nullptr; - } - auto pBitmap = pdfium::MakeRetain(); - pBitmap->Create(width, height, fx_format, static_cast(first_scan), - stride); - return pBitmap.Leak(); -} - -FPDF_EXPORT int FPDF_CALLCONV FPDFBitmap_GetFormat(FPDF_BITMAP bitmap) { - if (!bitmap) - return FPDFBitmap_Unknown; - - FXDIB_Format format = CFXBitmapFromFPDFBitmap(bitmap)->GetFormat(); - switch (format) { - case FXDIB_8bppRgb: - case FXDIB_8bppMask: - return FPDFBitmap_Gray; - case FXDIB_Rgb: - return FPDFBitmap_BGR; - case FXDIB_Rgb32: - return FPDFBitmap_BGRx; - case FXDIB_Argb: - return FPDFBitmap_BGRA; - default: - return FPDFBitmap_Unknown; - } -} - -FPDF_EXPORT void FPDF_CALLCONV FPDFBitmap_FillRect(FPDF_BITMAP bitmap, - int left, - int top, - int width, - int height, - FPDF_DWORD color) { - if (!bitmap) - return; - - CFX_DefaultRenderDevice device; - RetainPtr pBitmap(CFXBitmapFromFPDFBitmap(bitmap)); - device.Attach(pBitmap, false, nullptr, false); - if (!pBitmap->HasAlpha()) - color |= 0xFF000000; - FX_RECT rect(left, top, left + width, top + height); - device.FillRect(&rect, color); -} - -FPDF_EXPORT void* FPDF_CALLCONV FPDFBitmap_GetBuffer(FPDF_BITMAP bitmap) { - return bitmap ? CFXBitmapFromFPDFBitmap(bitmap)->GetBuffer() : nullptr; -} - -FPDF_EXPORT int FPDF_CALLCONV FPDFBitmap_GetWidth(FPDF_BITMAP bitmap) { - return bitmap ? CFXBitmapFromFPDFBitmap(bitmap)->GetWidth() : 0; -} - -FPDF_EXPORT int FPDF_CALLCONV FPDFBitmap_GetHeight(FPDF_BITMAP bitmap) { - return bitmap ? CFXBitmapFromFPDFBitmap(bitmap)->GetHeight() : 0; -} - -FPDF_EXPORT int FPDF_CALLCONV FPDFBitmap_GetStride(FPDF_BITMAP bitmap) { - return bitmap ? CFXBitmapFromFPDFBitmap(bitmap)->GetPitch() : 0; -} - -FPDF_EXPORT void FPDF_CALLCONV FPDFBitmap_Destroy(FPDF_BITMAP bitmap) { - RetainPtr destroyer; - destroyer.Unleak(CFXBitmapFromFPDFBitmap(bitmap)); -} - -void FPDF_RenderPage_Retail(CPDF_PageRenderContext* pContext, - FPDF_PAGE page, - int start_x, - int start_y, - int size_x, - int size_y, - int rotate, - int flags, - bool bNeedToRestore, - IPDFSDK_PauseAdapter* pause) { - CPDF_Page* pPage = CPDFPageFromFPDFPage(page); - if (!pPage) - return; - - RenderPageImpl(pContext, pPage, pPage->GetDisplayMatrix( - start_x, start_y, size_x, size_y, rotate), - FX_RECT(start_x, start_y, start_x + size_x, start_y + size_y), - flags, bNeedToRestore, pause); -} - -FPDF_EXPORT int FPDF_CALLCONV FPDF_GetPageSizeByIndex(FPDF_DOCUMENT document, - int page_index, - double* width, - double* height) { - UnderlyingDocumentType* pDoc = UnderlyingFromFPDFDocument(document); - if (!pDoc) - return false; - -#ifdef PDF_ENABLE_XFA - int count = pDoc->GetPageCount(); - if (page_index < 0 || page_index >= count) - return false; - RetainPtr pPage = pDoc->GetXFAPage(page_index); - if (!pPage) - return false; - *width = pPage->GetPageWidth(); - *height = pPage->GetPageHeight(); -#else // PDF_ENABLE_XFA - CPDF_Dictionary* pDict = pDoc->GetPage(page_index); - if (!pDict) - return false; - - CPDF_Page page(pDoc, pDict, true); - *width = page.GetPageWidth(); - *height = page.GetPageHeight(); -#endif // PDF_ENABLE_XFA - - return true; -} - -FPDF_EXPORT FPDF_BOOL FPDF_CALLCONV -FPDF_VIEWERREF_GetPrintScaling(FPDF_DOCUMENT document) { - CPDF_Document* pDoc = CPDFDocumentFromFPDFDocument(document); - if (!pDoc) - return true; - CPDF_ViewerPreferences viewRef(pDoc); - return viewRef.PrintScaling(); -} - -FPDF_EXPORT int FPDF_CALLCONV -FPDF_VIEWERREF_GetNumCopies(FPDF_DOCUMENT document) { - CPDF_Document* pDoc = CPDFDocumentFromFPDFDocument(document); - if (!pDoc) - return 1; - CPDF_ViewerPreferences viewRef(pDoc); - return viewRef.NumCopies(); -} - -FPDF_EXPORT FPDF_PAGERANGE FPDF_CALLCONV -FPDF_VIEWERREF_GetPrintPageRange(FPDF_DOCUMENT document) { - CPDF_Document* pDoc = CPDFDocumentFromFPDFDocument(document); - if (!pDoc) - return nullptr; - CPDF_ViewerPreferences viewRef(pDoc); - return viewRef.PrintPageRange(); -} - -FPDF_EXPORT FPDF_DUPLEXTYPE FPDF_CALLCONV -FPDF_VIEWERREF_GetDuplex(FPDF_DOCUMENT document) { - CPDF_Document* pDoc = CPDFDocumentFromFPDFDocument(document); - if (!pDoc) - return DuplexUndefined; - CPDF_ViewerPreferences viewRef(pDoc); - ByteString duplex = viewRef.Duplex(); - if ("Simplex" == duplex) - return Simplex; - if ("DuplexFlipShortEdge" == duplex) - return DuplexFlipShortEdge; - if ("DuplexFlipLongEdge" == duplex) - return DuplexFlipLongEdge; - return DuplexUndefined; -} - -FPDF_EXPORT unsigned long FPDF_CALLCONV -FPDF_VIEWERREF_GetName(FPDF_DOCUMENT document, - FPDF_BYTESTRING key, - char* buffer, - unsigned long length) { - CPDF_Document* pDoc = CPDFDocumentFromFPDFDocument(document); - if (!pDoc) - return 0; - - CPDF_ViewerPreferences viewRef(pDoc); - ByteString bsVal; - if (!viewRef.GenericName(key, &bsVal)) - return 0; - - unsigned long dwStringLen = bsVal.GetLength() + 1; - if (buffer && length >= dwStringLen) - memcpy(buffer, bsVal.c_str(), dwStringLen); - return dwStringLen; -} - -FPDF_EXPORT FPDF_DWORD FPDF_CALLCONV -FPDF_CountNamedDests(FPDF_DOCUMENT document) { - CPDF_Document* pDoc = CPDFDocumentFromFPDFDocument(document); - if (!pDoc) - return 0; - - const CPDF_Dictionary* pRoot = pDoc->GetRoot(); - if (!pRoot) - return 0; - - CPDF_NameTree nameTree(pDoc, "Dests"); - pdfium::base::CheckedNumeric count = nameTree.GetCount(); - CPDF_Dictionary* pDest = pRoot->GetDictFor("Dests"); - if (pDest) - count += pDest->GetCount(); - - if (!count.IsValid()) - return 0; - - return count.ValueOrDie(); -} - -FPDF_EXPORT FPDF_DEST FPDF_CALLCONV -FPDF_GetNamedDestByName(FPDF_DOCUMENT document, FPDF_BYTESTRING name) { - if (!name || name[0] == 0) - return nullptr; - - CPDF_Document* pDoc = CPDFDocumentFromFPDFDocument(document); - if (!pDoc) - return nullptr; - - CPDF_NameTree name_tree(pDoc, "Dests"); - return name_tree.LookupNamedDest(pDoc, PDF_DecodeText(ByteString(name))); -} - -#ifdef PDF_ENABLE_XFA -FPDF_EXPORT FPDF_RESULT FPDF_CALLCONV FPDF_BStr_Init(FPDF_BSTR* str) { - if (!str) - return -1; - - memset(str, 0, sizeof(FPDF_BSTR)); - return 0; -} - -FPDF_EXPORT FPDF_RESULT FPDF_CALLCONV FPDF_BStr_Set(FPDF_BSTR* str, - FPDF_LPCSTR bstr, - int length) { - if (!str || !bstr || !length) - return -1; - - if (length == -1) - length = strlen(bstr); - - if (length == 0) { - FPDF_BStr_Clear(str); - return 0; - } - - if (str->str && str->len < length) - str->str = FX_Realloc(char, str->str, length + 1); - else if (!str->str) - str->str = FX_Alloc(char, length + 1); - - str->str[length] = 0; - memcpy(str->str, bstr, length); - str->len = length; - - return 0; -} - -FPDF_EXPORT FPDF_RESULT FPDF_CALLCONV FPDF_BStr_Clear(FPDF_BSTR* str) { - if (!str) - return -1; - - if (str->str) { - FX_Free(str->str); - str->str = nullptr; - } - str->len = 0; - return 0; -} -#endif // PDF_ENABLE_XFA - -FPDF_EXPORT FPDF_DEST FPDF_CALLCONV FPDF_GetNamedDest(FPDF_DOCUMENT document, - int index, - void* buffer, - long* buflen) { - if (!buffer) - *buflen = 0; - - if (index < 0) - return nullptr; - - CPDF_Document* pDoc = CPDFDocumentFromFPDFDocument(document); - if (!pDoc) - return nullptr; - - const CPDF_Dictionary* pRoot = pDoc->GetRoot(); - if (!pRoot) - return nullptr; - - CPDF_Object* pDestObj = nullptr; - WideString wsName; - CPDF_NameTree nameTree(pDoc, "Dests"); - int count = nameTree.GetCount(); - if (index >= count) { - CPDF_Dictionary* pDest = pRoot->GetDictFor("Dests"); - if (!pDest) - return nullptr; - - pdfium::base::CheckedNumeric checked_count = count; - checked_count += pDest->GetCount(); - if (!checked_count.IsValid() || index >= checked_count.ValueOrDie()) - return nullptr; - - index -= count; - int i = 0; - ByteString bsName; - for (const auto& it : *pDest) { - bsName = it.first; - pDestObj = it.second.get(); - if (!pDestObj) - continue; - if (i == index) - break; - i++; - } - wsName = PDF_DecodeText(bsName); - } else { - pDestObj = nameTree.LookupValueAndName(index, &wsName); - } - if (!pDestObj) - return nullptr; - if (CPDF_Dictionary* pDict = pDestObj->AsDictionary()) { - pDestObj = pDict->GetArrayFor("D"); - if (!pDestObj) - return nullptr; - } - if (!pDestObj->IsArray()) - return nullptr; - - ByteString utf16Name = wsName.UTF16LE_Encode(); - int len = utf16Name.GetLength(); - if (!buffer) { - *buflen = len; - } else if (len <= *buflen) { - memcpy(buffer, utf16Name.c_str(), len); - *buflen = len; - } else { - *buflen = -1; - } - return (FPDF_DEST)pDestObj; -} diff --git a/fpdfsdk/fpdfview_c_api_test.c b/fpdfsdk/fpdfview_c_api_test.c deleted file mode 100644 index a0bcc13ff3..0000000000 --- a/fpdfsdk/fpdfview_c_api_test.c +++ /dev/null @@ -1,382 +0,0 @@ -// Copyright 2015 PDFium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -// This "C" (not "C++") file ensures that the public headers compile -// and link for "C" (and not just "C++"). - -#include - -#include "fpdfsdk/fpdfview_c_api_test.h" - -#include "public/fpdf_annot.h" -#include "public/fpdf_attachment.h" -#include "public/fpdf_catalog.h" -#include "public/fpdf_dataavail.h" -#include "public/fpdf_doc.h" -#include "public/fpdf_edit.h" -#include "public/fpdf_ext.h" -#include "public/fpdf_flatten.h" -#include "public/fpdf_formfill.h" -#include "public/fpdf_fwlevent.h" -#include "public/fpdf_ppo.h" -#include "public/fpdf_progressive.h" -#include "public/fpdf_save.h" -#include "public/fpdf_searchex.h" -#include "public/fpdf_structtree.h" -#include "public/fpdf_sysfontinfo.h" -#include "public/fpdf_text.h" -#include "public/fpdf_transformpage.h" -#include "public/fpdfview.h" - -// Scheme for avoiding LTO out of existence, warnings, etc. -typedef void (*fnptr)(void); // Legal generic function type for casts. -fnptr g_c_api_test_fnptr = NULL; // Extern, so can't know it doesn't change. -#define CHK(x) if ((fnptr)(x) == g_c_api_test_fnptr) return 0 - -// Function to call from gtest harness to ensure linker resolution. -int CheckPDFiumCApi() { - // fpdf_annot.h - CHK(FPDFAnnot_IsSupportedSubtype); - CHK(FPDFPage_CreateAnnot); - CHK(FPDFPage_GetAnnotCount); - CHK(FPDFPage_GetAnnot); - CHK(FPDFPage_GetAnnotIndex); - CHK(FPDFPage_CloseAnnot); - CHK(FPDFPage_RemoveAnnot); - CHK(FPDFAnnot_GetSubtype); - CHK(FPDFAnnot_IsObjectSupportedSubtype); - CHK(FPDFAnnot_UpdateObject); - CHK(FPDFAnnot_AppendObject); - CHK(FPDFAnnot_GetObjectCount); - CHK(FPDFAnnot_GetObject); - CHK(FPDFAnnot_RemoveObject); - CHK(FPDFAnnot_SetColor); - CHK(FPDFAnnot_GetColor); - CHK(FPDFAnnot_HasAttachmentPoints); - CHK(FPDFAnnot_SetAttachmentPoints); - CHK(FPDFAnnot_CountAttachmentPoints); - CHK(FPDFAnnot_GetAttachmentPoints); - CHK(FPDFAnnot_SetRect); - CHK(FPDFAnnot_GetRect); - CHK(FPDFAnnot_HasKey); - CHK(FPDFAnnot_GetValueType); - CHK(FPDFAnnot_SetStringValue); - CHK(FPDFAnnot_GetStringValue); - CHK(FPDFAnnot_SetAP); - CHK(FPDFAnnot_GetAP); - CHK(FPDFAnnot_GetLinkedAnnot); - CHK(FPDFAnnot_GetFlags); - CHK(FPDFAnnot_SetFlags); - CHK(FPDFAnnot_GetFormFieldFlags); - CHK(FPDFAnnot_GetFormFieldAtPoint); - - // fpdf_attachment.h - CHK(FPDFDoc_GetAttachmentCount); - CHK(FPDFDoc_AddAttachment); - CHK(FPDFDoc_GetAttachment); - CHK(FPDFDoc_DeleteAttachment); - CHK(FPDFAttachment_GetName); - CHK(FPDFAttachment_HasKey); - CHK(FPDFAttachment_GetValueType); - CHK(FPDFAttachment_SetStringValue); - CHK(FPDFAttachment_GetStringValue); - CHK(FPDFAttachment_SetFile); - CHK(FPDFAttachment_GetFile); - - // fpdf_catalog.h - CHK(FPDFCatalog_IsTagged); - - // fpdf_dataavail.h - CHK(FPDFAvail_Create); - CHK(FPDFAvail_Destroy); - CHK(FPDFAvail_IsDocAvail); - CHK(FPDFAvail_GetDocument); - CHK(FPDFAvail_GetFirstPageNum); - CHK(FPDFAvail_IsPageAvail); - CHK(FPDFAvail_IsFormAvail); - CHK(FPDFAvail_IsLinearized); - - // fpdf_doc.h - CHK(FPDFBookmark_GetFirstChild); - CHK(FPDFBookmark_GetNextSibling); - CHK(FPDFBookmark_GetTitle); - CHK(FPDFBookmark_Find); - CHK(FPDFBookmark_GetDest); - CHK(FPDFBookmark_GetAction); - CHK(FPDFAction_GetType); - CHK(FPDFAction_GetDest); - CHK(FPDFAction_GetFilePath); - CHK(FPDFAction_GetURIPath); - CHK(FPDFDest_GetDestPageIndex); - CHK(FPDFDest_GetPageIndex); - CHK(FPDFDest_GetLocationInPage); - CHK(FPDFDest_GetView); - CHK(FPDFLink_GetLinkAtPoint); - CHK(FPDFLink_GetLinkZOrderAtPoint); - CHK(FPDFLink_GetDest); - CHK(FPDFLink_GetAction); - CHK(FPDFLink_Enumerate); - CHK(FPDFLink_GetAnnotRect); - CHK(FPDFLink_CountQuadPoints); - CHK(FPDFLink_GetQuadPoints); - CHK(FPDF_GetMetaText); - CHK(FPDF_GetPageLabel); - - // fpdf_edit.h - CHK(FPDF_CreateNewDocument); - CHK(FPDFPage_New); - CHK(FPDFPage_Delete); - CHK(FPDFPage_GetRotation); - CHK(FPDFPage_SetRotation); - CHK(FPDFPage_InsertObject); - CHK(FPDFPage_RemoveObject); - CHK(FPDFPage_CountObject); - CHK(FPDFPage_CountObjects); - CHK(FPDFPage_GetObject); - CHK(FPDFPage_HasTransparency); - CHK(FPDFPage_GenerateContent); - CHK(FPDFPageObj_Destroy); - CHK(FPDFPageObj_HasTransparency); - CHK(FPDFPageObj_GetBounds); - CHK(FPDFPageObj_GetType); - CHK(FPDFPageObj_SetBlendMode); - CHK(FPDFPageObj_Transform); - CHK(FPDFPage_TransformAnnots); - CHK(FPDFPageObj_NewImageObj); - CHK(FPDFPageObj_CountMarks); - CHK(FPDFPageObj_GetMark); - CHK(FPDFPageObjMark_GetName); - CHK(FPDFImageObj_LoadJpegFile); - CHK(FPDFImageObj_LoadJpegFileInline); - CHK(FPDFImageObj_SetMatrix); - CHK(FPDFImageObj_SetBitmap); - CHK(FPDFImageObj_GetBitmap); - CHK(FPDFImageObj_GetImageDataDecoded); - CHK(FPDFImageObj_GetImageDataRaw); - CHK(FPDFImageObj_GetImageFilterCount); - CHK(FPDFImageObj_GetImageFilter); - CHK(FPDFImageObj_GetImageMetadata); - CHK(FPDFPageObj_CreateNewPath); - CHK(FPDFPageObj_CreateNewRect); - CHK(FPDFPath_SetStrokeColor); - CHK(FPDFPath_GetStrokeColor); - CHK(FPDFPath_SetStrokeWidth); - CHK(FPDFPath_SetFillColor); - CHK(FPDFPath_GetFillColor); - CHK(FPDFPath_CountSegments); - CHK(FPDFPath_GetPathSegment); - CHK(FPDFPathSegment_GetPoint); - CHK(FPDFPathSegment_GetType); - CHK(FPDFPathSegment_GetClose); - CHK(FPDFPath_MoveTo); - CHK(FPDFPath_LineTo); - CHK(FPDFPath_BezierTo); - CHK(FPDFPath_Close); - CHK(FPDFPath_SetDrawMode); - CHK(FPDFPath_SetLineCap); - CHK(FPDFPath_SetLineJoin); - CHK(FPDFPageObj_NewTextObj); - CHK(FPDFText_SetText); - CHK(FPDFText_SetFillColor); - CHK(FPDFText_LoadFont); - CHK(FPDFFont_Close); - CHK(FPDFPageObj_CreateTextObj); - - // fpdf_ext.h - CHK(FSDK_SetUnSpObjProcessHandler); - CHK(FPDFDoc_GetPageMode); - - // fpdf_flatten.h - CHK(FPDFPage_Flatten); - - // fpdf_fwlevent.h - no exports. - - // fpdf_formfill.h - CHK(FPDFDOC_InitFormFillEnvironment); - CHK(FPDFDOC_ExitFormFillEnvironment); - CHK(FORM_OnAfterLoadPage); - CHK(FORM_OnBeforeClosePage); - CHK(FORM_DoDocumentJSAction); - CHK(FORM_DoDocumentOpenAction); - CHK(FORM_DoDocumentAAction); - CHK(FORM_DoPageAAction); - CHK(FORM_OnMouseMove); - CHK(FORM_OnFocus); - CHK(FORM_OnLButtonDown); - CHK(FORM_OnLButtonUp); -#ifdef PDF_ENABLE_XFA - CHK(FORM_OnRButtonDown); - CHK(FORM_OnRButtonUp); -#endif - CHK(FORM_OnKeyDown); - CHK(FORM_OnKeyUp); - CHK(FORM_OnChar); - CHK(FORM_GetSelectedText); - CHK(FORM_ReplaceSelection); - CHK(FORM_ForceToKillFocus); - CHK(FPDFPage_HasFormFieldAtPoint); - CHK(FPDFPage_FormFieldZOrderAtPoint); - CHK(FPDF_SetFormFieldHighlightColor); - CHK(FPDF_SetFormFieldHighlightAlpha); - CHK(FPDF_RemoveFormFieldHighlight); - CHK(FPDF_FFLDraw); -#ifdef _SKIA_SUPPORT_ - CHK(FPDF_FFLRecord); -#endif - CHK(FPDF_GetFormType); -#ifdef PDF_ENABLE_XFA - CHK(FPDF_LoadXFA); - CHK(FPDF_Widget_Undo); - CHK(FPDF_Widget_Redo); - CHK(FPDF_Widget_SelectAll); - CHK(FPDF_Widget_Copy); - CHK(FPDF_Widget_Cut); - CHK(FPDF_Widget_Paste); - CHK(FPDF_Widget_ReplaceSpellCheckWord); - CHK(FPDF_Widget_GetSpellCheckWords); - CHK(FPDF_StringHandleCounts); - CHK(FPDF_StringHandleGetStringByIndex); - CHK(FPDF_StringHandleRelease); - CHK(FPDF_StringHandleAddString); -#endif - - // fpdf_ppo.h - CHK(FPDF_ImportPages); - CHK(FPDF_ImportNPagesToOne); - CHK(FPDF_CopyViewerPreferences); - - // fpdf_progressive.h - CHK(FPDF_RenderPageBitmap_Start); - CHK(FPDF_RenderPage_Continue); - CHK(FPDF_RenderPage_Close); - - // fpdf_save.h - CHK(FPDF_SaveAsCopy); - CHK(FPDF_SaveWithVersion); - - // fpdf_searchex.h - CHK(FPDFText_GetCharIndexFromTextIndex); - CHK(FPDFText_GetTextIndexFromCharIndex); - - // fpdf_structtree.h - CHK(FPDF_StructTree_GetForPage); - CHK(FPDF_StructTree_Close); - CHK(FPDF_StructTree_CountChildren); - CHK(FPDF_StructTree_GetChildAtIndex); - CHK(FPDF_StructElement_GetAltText); - CHK(FPDF_StructElement_GetMarkedContentID); - CHK(FPDF_StructElement_GetType); - CHK(FPDF_StructElement_GetTitle); - CHK(FPDF_StructElement_CountChildren); - CHK(FPDF_StructElement_GetChildAtIndex); - - // fpdf_sysfontinfo.h - CHK(FPDF_GetDefaultTTFMap); - CHK(FPDF_AddInstalledFont); - CHK(FPDF_SetSystemFontInfo); - CHK(FPDF_GetDefaultSystemFontInfo); - CHK(FPDF_FreeDefaultSystemFontInfo); - - // fpdf_text.h - CHK(FPDFText_LoadPage); - CHK(FPDFText_ClosePage); - CHK(FPDFText_CountChars); - CHK(FPDFText_GetUnicode); - CHK(FPDFText_GetFontSize); - CHK(FPDFText_GetCharBox); - CHK(FPDFText_GetCharOrigin); - CHK(FPDFText_GetCharIndexAtPos); - CHK(FPDFText_GetText); - CHK(FPDFText_CountRects); - CHK(FPDFText_GetRect); - CHK(FPDFText_GetBoundedText); - CHK(FPDFText_FindStart); - CHK(FPDFText_FindNext); - CHK(FPDFText_FindPrev); - CHK(FPDFText_GetSchResultIndex); - CHK(FPDFText_GetSchCount); - CHK(FPDFText_FindClose); - CHK(FPDFLink_LoadWebLinks); - CHK(FPDFLink_CountWebLinks); - CHK(FPDFLink_GetURL); - CHK(FPDFLink_CountRects); - CHK(FPDFLink_GetRect); - CHK(FPDFLink_CloseWebLinks); - - // fpdf_transformpage.h - CHK(FPDFPage_SetMediaBox); - CHK(FPDFPage_SetCropBox); - CHK(FPDFPage_GetMediaBox); - CHK(FPDFPage_GetCropBox); - CHK(FPDFPage_TransFormWithClip); - CHK(FPDFPageObj_TransformClipPath); - CHK(FPDF_CreateClipPath); - CHK(FPDF_DestroyClipPath); - CHK(FPDFPage_InsertClipPath); - - // fpdfview.h - CHK(FPDF_InitLibrary); - CHK(FPDF_InitLibraryWithConfig); - CHK(FPDF_DestroyLibrary); - CHK(FPDF_SetSandBoxPolicy); -#if defined(_WIN32) -#if defined(PDFIUM_PRINT_TEXT_WITH_GDI) - CHK(FPDF_SetTypefaceAccessibleFunc); - CHK(FPDF_SetPrintTextWithGDI); -#endif - CHK(FPDF_SetPrintPostscriptLevel); - CHK(FPDF_SetPrintMode); -#endif - CHK(FPDF_LoadDocument); - CHK(FPDF_LoadMemDocument); - CHK(FPDF_LoadCustomDocument); - CHK(FPDF_GetFileVersion); - CHK(FPDF_GetLastError); - CHK(FPDF_GetDocPermissions); - CHK(FPDF_GetSecurityHandlerRevision); - CHK(FPDF_GetPageCount); - CHK(FPDF_LoadPage); - CHK(FPDF_GetPageWidth); - CHK(FPDF_GetPageHeight); - CHK(FPDF_GetPageBoundingBox); - CHK(FPDF_GetPageSizeByIndex); -#ifdef _WIN32 - CHK(FPDF_RenderPage); -#endif - CHK(FPDF_RenderPageBitmap); - CHK(FPDF_RenderPageBitmapWithMatrix); -#ifdef _SKIA_SUPPORT_ - CHK(FPDF_RenderPageSkp); -#endif - CHK(FPDF_ClosePage); - CHK(FPDF_CloseDocument); - CHK(FPDF_DeviceToPage); - CHK(FPDF_PageToDevice); - CHK(FPDFBitmap_Create); - CHK(FPDFBitmap_CreateEx); - CHK(FPDFBitmap_GetFormat); - CHK(FPDFBitmap_FillRect); - CHK(FPDFBitmap_GetBuffer); - CHK(FPDFBitmap_GetWidth); - CHK(FPDFBitmap_GetHeight); - CHK(FPDFBitmap_GetStride); - CHK(FPDFBitmap_Destroy); - CHK(FPDF_VIEWERREF_GetPrintScaling); - CHK(FPDF_VIEWERREF_GetNumCopies); - CHK(FPDF_VIEWERREF_GetPrintPageRange); - CHK(FPDF_VIEWERREF_GetDuplex); - CHK(FPDF_VIEWERREF_GetName); - CHK(FPDF_CountNamedDests); - CHK(FPDF_GetNamedDestByName); - CHK(FPDF_GetNamedDest); -#ifdef PDF_ENABLE_XFA - CHK(FPDF_BStr_Init); - CHK(FPDF_BStr_Set); - CHK(FPDF_BStr_Clear); -#endif - - return 1; -} - -#undef CHK diff --git a/fpdfsdk/fpdfview_c_api_test.h b/fpdfsdk/fpdfview_c_api_test.h deleted file mode 100644 index d5b84a27a9..0000000000 --- a/fpdfsdk/fpdfview_c_api_test.h +++ /dev/null @@ -1,20 +0,0 @@ -// Copyright 2015 PDFium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#ifndef FPDFSDK_FPDFVIEW_C_API_TEST_H_ -#define FPDFSDK_FPDFVIEW_C_API_TEST_H_ - -#ifdef __cplusplus -extern "C" { -#endif - -// Function to call from gtest harness to ensure linker resolution. Returns -// 1 on success or 0 on error. -int CheckPDFiumCApi(); - -#ifdef __cplusplus -} -#endif - -#endif // FPDFSDK_FPDFVIEW_C_API_TEST_H_ diff --git a/fpdfsdk/fpdfview_embeddertest.cpp b/fpdfsdk/fpdfview_embeddertest.cpp deleted file mode 100644 index ab7606479d..0000000000 --- a/fpdfsdk/fpdfview_embeddertest.cpp +++ /dev/null @@ -1,605 +0,0 @@ -// Copyright 2015 PDFium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#include -#include -#include -#include - -#include "fpdfsdk/fpdfview_c_api_test.h" -#include "public/fpdfview.h" -#include "testing/embedder_test.h" -#include "testing/gtest/include/gtest/gtest.h" -#include "testing/utils/path_service.h" - -namespace { - -class MockDownloadHints : public FX_DOWNLOADHINTS { - public: - static void SAddSegment(FX_DOWNLOADHINTS* pThis, size_t offset, size_t size) { - } - - MockDownloadHints() { - FX_DOWNLOADHINTS::version = 1; - FX_DOWNLOADHINTS::AddSegment = SAddSegment; - } - - ~MockDownloadHints() {} -}; - -} // namespace - -TEST(fpdf, CApiTest) { - EXPECT_TRUE(CheckPDFiumCApi()); -} - -class FPDFViewEmbeddertest : public EmbedderTest { - protected: - void TestRenderPageBitmapWithMatrix(FPDF_PAGE page, - const int bitmap_width, - const int bitmap_height, - const FS_MATRIX& matrix, - const FS_RECTF& rect, - const char* expected_md5); -}; - -TEST_F(FPDFViewEmbeddertest, Document) { - EXPECT_TRUE(OpenDocument("about_blank.pdf")); - EXPECT_EQ(1, GetPageCount()); - EXPECT_EQ(0, GetFirstPageNum()); - - int version; - EXPECT_TRUE(FPDF_GetFileVersion(document(), &version)); - EXPECT_EQ(14, version); - - EXPECT_EQ(0xFFFFFFFF, FPDF_GetDocPermissions(document())); - EXPECT_EQ(-1, FPDF_GetSecurityHandlerRevision(document())); -} - -TEST_F(FPDFViewEmbeddertest, LoadNonexistentDocument) { - FPDF_DOCUMENT doc = FPDF_LoadDocument("nonexistent_document.pdf", ""); - ASSERT_FALSE(doc); - EXPECT_EQ(static_cast(FPDF_GetLastError()), FPDF_ERR_FILE); -} - -// See bug 465. -TEST_F(FPDFViewEmbeddertest, EmptyDocument) { - EXPECT_TRUE(CreateEmptyDocument()); - - { - int version = 42; - EXPECT_FALSE(FPDF_GetFileVersion(document(), &version)); - EXPECT_EQ(0, version); - } - - { -#ifndef PDF_ENABLE_XFA - const unsigned long kExpected = 0; -#else - const unsigned long kExpected = static_cast(-1); -#endif - EXPECT_EQ(kExpected, FPDF_GetDocPermissions(document())); - } - - EXPECT_EQ(-1, FPDF_GetSecurityHandlerRevision(document())); - - EXPECT_EQ(0, FPDF_GetPageCount(document())); - - EXPECT_TRUE(FPDF_VIEWERREF_GetPrintScaling(document())); - EXPECT_EQ(1, FPDF_VIEWERREF_GetNumCopies(document())); - EXPECT_EQ(DuplexUndefined, FPDF_VIEWERREF_GetDuplex(document())); - - char buf[100]; - EXPECT_EQ(0U, FPDF_VIEWERREF_GetName(document(), "foo", nullptr, 0)); - EXPECT_EQ(0U, FPDF_VIEWERREF_GetName(document(), "foo", buf, sizeof(buf))); - - EXPECT_EQ(0u, FPDF_CountNamedDests(document())); -} - -TEST_F(FPDFViewEmbeddertest, LinearizedDocument) { - EXPECT_TRUE(OpenDocumentLinearized("feature_linearized_loading.pdf")); - int version; - EXPECT_TRUE(FPDF_GetFileVersion(document(), &version)); - EXPECT_EQ(16, version); -} - -TEST_F(FPDFViewEmbeddertest, Page) { - EXPECT_TRUE(OpenDocument("about_blank.pdf")); - FPDF_PAGE page = LoadPage(0); - EXPECT_NE(nullptr, page); - - EXPECT_EQ(612.0, FPDF_GetPageWidth(page)); - EXPECT_EQ(792.0, FPDF_GetPageHeight(page)); - - FS_RECTF rect; - EXPECT_TRUE(FPDF_GetPageBoundingBox(page, &rect)); - EXPECT_EQ(0.0, rect.left); - EXPECT_EQ(0.0, rect.bottom); - EXPECT_EQ(612.0, rect.right); - EXPECT_EQ(792.0, rect.top); - - UnloadPage(page); - EXPECT_EQ(nullptr, LoadPage(1)); -} - -TEST_F(FPDFViewEmbeddertest, ViewerRefDummy) { - EXPECT_TRUE(OpenDocument("about_blank.pdf")); - EXPECT_TRUE(FPDF_VIEWERREF_GetPrintScaling(document())); - EXPECT_EQ(1, FPDF_VIEWERREF_GetNumCopies(document())); - EXPECT_EQ(DuplexUndefined, FPDF_VIEWERREF_GetDuplex(document())); - - char buf[100]; - EXPECT_EQ(0U, FPDF_VIEWERREF_GetName(document(), "foo", nullptr, 0)); - EXPECT_EQ(0U, FPDF_VIEWERREF_GetName(document(), "foo", buf, sizeof(buf))); -} - -TEST_F(FPDFViewEmbeddertest, ViewerRef) { - EXPECT_TRUE(OpenDocument("viewer_ref.pdf")); - EXPECT_TRUE(FPDF_VIEWERREF_GetPrintScaling(document())); - EXPECT_EQ(5, FPDF_VIEWERREF_GetNumCopies(document())); - EXPECT_EQ(DuplexUndefined, FPDF_VIEWERREF_GetDuplex(document())); - - // Test some corner cases. - char buf[100]; - EXPECT_EQ(0U, FPDF_VIEWERREF_GetName(document(), "", buf, sizeof(buf))); - EXPECT_EQ(0U, FPDF_VIEWERREF_GetName(document(), "foo", nullptr, 0)); - EXPECT_EQ(0U, FPDF_VIEWERREF_GetName(document(), "foo", buf, sizeof(buf))); - - // Make sure |buf| does not get written into when it appears to be too small. - // NOLINTNEXTLINE(runtime/printf) - strcpy(buf, "ABCD"); - EXPECT_EQ(4U, FPDF_VIEWERREF_GetName(document(), "Foo", buf, 1)); - EXPECT_STREQ("ABCD", buf); - - // Note "Foo" is a different key from "foo". - EXPECT_EQ(4U, - FPDF_VIEWERREF_GetName(document(), "Foo", nullptr, sizeof(buf))); - ASSERT_EQ(4U, FPDF_VIEWERREF_GetName(document(), "Foo", buf, sizeof(buf))); - EXPECT_STREQ("foo", buf); - - // Try to retrieve a boolean and an integer. - EXPECT_EQ( - 0U, FPDF_VIEWERREF_GetName(document(), "HideToolbar", buf, sizeof(buf))); - EXPECT_EQ(0U, - FPDF_VIEWERREF_GetName(document(), "NumCopies", buf, sizeof(buf))); - - // Try more valid cases. - ASSERT_EQ(4U, - FPDF_VIEWERREF_GetName(document(), "Direction", buf, sizeof(buf))); - EXPECT_STREQ("R2L", buf); - ASSERT_EQ(8U, - FPDF_VIEWERREF_GetName(document(), "ViewArea", buf, sizeof(buf))); - EXPECT_STREQ("CropBox", buf); -} - -TEST_F(FPDFViewEmbeddertest, NamedDests) { - EXPECT_TRUE(OpenDocument("named_dests.pdf")); - long buffer_size; - char fixed_buffer[512]; - FPDF_DEST dest; - - // Query the size of the first item. - buffer_size = 2000000; // Absurdly large, check not used for this case. - dest = FPDF_GetNamedDest(document(), 0, nullptr, &buffer_size); - EXPECT_NE(nullptr, dest); - EXPECT_EQ(12, buffer_size); - - // Try to retrieve the first item with too small a buffer. - buffer_size = 10; - dest = FPDF_GetNamedDest(document(), 0, fixed_buffer, &buffer_size); - EXPECT_NE(nullptr, dest); - EXPECT_EQ(-1, buffer_size); - - // Try to retrieve the first item with correctly sized buffer. Item is - // taken from Dests NameTree in named_dests.pdf. - buffer_size = 12; - dest = FPDF_GetNamedDest(document(), 0, fixed_buffer, &buffer_size); - EXPECT_NE(nullptr, dest); - EXPECT_EQ(12, buffer_size); - EXPECT_EQ(std::string("F\0i\0r\0s\0t\0\0\0", 12), - std::string(fixed_buffer, buffer_size)); - - // Try to retrieve the second item with ample buffer. Item is taken - // from Dests NameTree but has a sub-dictionary in named_dests.pdf. - buffer_size = sizeof(fixed_buffer); - dest = FPDF_GetNamedDest(document(), 1, fixed_buffer, &buffer_size); - EXPECT_NE(nullptr, dest); - EXPECT_EQ(10, buffer_size); - EXPECT_EQ(std::string("N\0e\0x\0t\0\0\0", 10), - std::string(fixed_buffer, buffer_size)); - - // Try to retrieve third item with ample buffer. Item is taken - // from Dests NameTree but has a bad sub-dictionary in named_dests.pdf. - // in named_dests.pdf). - buffer_size = sizeof(fixed_buffer); - dest = FPDF_GetNamedDest(document(), 2, fixed_buffer, &buffer_size); - EXPECT_EQ(nullptr, dest); - EXPECT_EQ(sizeof(fixed_buffer), - static_cast(buffer_size)); // unmodified. - - // Try to retrieve the forth item with ample buffer. Item is taken - // from Dests NameTree but has a vale of the wrong type in named_dests.pdf. - buffer_size = sizeof(fixed_buffer); - dest = FPDF_GetNamedDest(document(), 3, fixed_buffer, &buffer_size); - EXPECT_EQ(nullptr, dest); - EXPECT_EQ(sizeof(fixed_buffer), - static_cast(buffer_size)); // unmodified. - - // Try to retrieve fifth item with ample buffer. Item taken from the - // old-style Dests dictionary object in named_dests.pdf. - buffer_size = sizeof(fixed_buffer); - dest = FPDF_GetNamedDest(document(), 4, fixed_buffer, &buffer_size); - EXPECT_NE(nullptr, dest); - EXPECT_EQ(30, buffer_size); - EXPECT_EQ(std::string("F\0i\0r\0s\0t\0A\0l\0t\0e\0r\0n\0a\0t\0e\0\0\0", 30), - std::string(fixed_buffer, buffer_size)); - - // Try to retrieve sixth item with ample buffer. Item istaken from the - // old-style Dests dictionary object but has a sub-dictionary in - // named_dests.pdf. - buffer_size = sizeof(fixed_buffer); - dest = FPDF_GetNamedDest(document(), 5, fixed_buffer, &buffer_size); - EXPECT_NE(nullptr, dest); - EXPECT_EQ(28, buffer_size); - EXPECT_EQ(std::string("L\0a\0s\0t\0A\0l\0t\0e\0r\0n\0a\0t\0e\0\0\0", 28), - std::string(fixed_buffer, buffer_size)); - - // Try to retrieve non-existent item with ample buffer. - buffer_size = sizeof(fixed_buffer); - dest = FPDF_GetNamedDest(document(), 6, fixed_buffer, &buffer_size); - EXPECT_EQ(nullptr, dest); - EXPECT_EQ(sizeof(fixed_buffer), - static_cast(buffer_size)); // unmodified. - - // Try to underflow/overflow the integer index. - buffer_size = sizeof(fixed_buffer); - dest = FPDF_GetNamedDest(document(), std::numeric_limits::max(), - fixed_buffer, &buffer_size); - EXPECT_EQ(nullptr, dest); - EXPECT_EQ(sizeof(fixed_buffer), - static_cast(buffer_size)); // unmodified. - - buffer_size = sizeof(fixed_buffer); - dest = FPDF_GetNamedDest(document(), std::numeric_limits::min(), - fixed_buffer, &buffer_size); - EXPECT_EQ(nullptr, dest); - EXPECT_EQ(sizeof(fixed_buffer), - static_cast(buffer_size)); // unmodified. - - buffer_size = sizeof(fixed_buffer); - dest = FPDF_GetNamedDest(document(), -1, fixed_buffer, &buffer_size); - EXPECT_EQ(nullptr, dest); - EXPECT_EQ(sizeof(fixed_buffer), - static_cast(buffer_size)); // unmodified. -} - -TEST_F(FPDFViewEmbeddertest, NamedDestsByName) { - EXPECT_TRUE(OpenDocument("named_dests.pdf")); - - // Null pointer returns nullptr. - FPDF_DEST dest = FPDF_GetNamedDestByName(document(), nullptr); - EXPECT_EQ(nullptr, dest); - - // Empty string returns nullptr. - dest = FPDF_GetNamedDestByName(document(), ""); - EXPECT_EQ(nullptr, dest); - - // Item from Dests NameTree. - dest = FPDF_GetNamedDestByName(document(), "First"); - EXPECT_NE(nullptr, dest); - - long ignore_len = 0; - FPDF_DEST dest_by_index = - FPDF_GetNamedDest(document(), 0, nullptr, &ignore_len); - EXPECT_EQ(dest_by_index, dest); - - // Item from Dests dictionary. - dest = FPDF_GetNamedDestByName(document(), "FirstAlternate"); - EXPECT_NE(nullptr, dest); - - ignore_len = 0; - dest_by_index = FPDF_GetNamedDest(document(), 4, nullptr, &ignore_len); - EXPECT_EQ(dest_by_index, dest); - - // Bad value type for item from Dests NameTree array. - dest = FPDF_GetNamedDestByName(document(), "WrongType"); - EXPECT_EQ(nullptr, dest); - - // No such destination in either Dest NameTree or dictionary. - dest = FPDF_GetNamedDestByName(document(), "Bogus"); - EXPECT_EQ(nullptr, dest); -} - -// The following tests pass if the document opens without crashing. -TEST_F(FPDFViewEmbeddertest, Crasher_113) { - EXPECT_TRUE(OpenDocument("bug_113.pdf")); -} - -TEST_F(FPDFViewEmbeddertest, Crasher_451830) { - // Document is damaged and can't be opened. - EXPECT_FALSE(OpenDocument("bug_451830.pdf")); -} - -TEST_F(FPDFViewEmbeddertest, Crasher_452455) { - EXPECT_TRUE(OpenDocument("bug_452455.pdf")); - FPDF_PAGE page = LoadPage(0); - EXPECT_NE(nullptr, page); - UnloadPage(page); -} - -TEST_F(FPDFViewEmbeddertest, Crasher_454695) { - // Document is damaged and can't be opened. - EXPECT_FALSE(OpenDocument("bug_454695.pdf")); -} - -TEST_F(FPDFViewEmbeddertest, Crasher_572871) { - EXPECT_TRUE(OpenDocument("bug_572871.pdf")); -} - -// It tests that document can still be loaded even the trailer has no 'Size' -// field if other information is right. -TEST_F(FPDFViewEmbeddertest, Failed_213) { - EXPECT_TRUE(OpenDocument("bug_213.pdf")); -} - -// The following tests pass if the document opens without infinite looping. -TEST_F(FPDFViewEmbeddertest, Hang_298) { - EXPECT_FALSE(OpenDocument("bug_298.pdf")); -} - -TEST_F(FPDFViewEmbeddertest, Crasher_773229) { - EXPECT_TRUE(OpenDocument("bug_773229.pdf")); -} - -// Test if the document opens without infinite looping. -// Previously this test will hang in a loop inside LoadAllCrossRefV4. After -// the fix, LoadAllCrossRefV4 will return false after detecting a cross -// reference loop. Cross references will be rebuilt successfully. -TEST_F(FPDFViewEmbeddertest, CrossRefV4Loop) { - EXPECT_TRUE(OpenDocument("bug_xrefv4_loop.pdf")); - MockDownloadHints hints; - - // Make sure calling FPDFAvail_IsDocAvail() on this file does not infinite - // loop either. See bug 875. - int ret = PDF_DATA_NOTAVAIL; - while (ret == PDF_DATA_NOTAVAIL) - ret = FPDFAvail_IsDocAvail(avail_, &hints); - EXPECT_EQ(PDF_DATA_AVAIL, ret); -} - -// The test should pass when circular references to ParseIndirectObject will not -// cause infinite loop. -TEST_F(FPDFViewEmbeddertest, Hang_343) { - EXPECT_FALSE(OpenDocument("bug_343.pdf")); -} - -// The test should pass when the absence of 'Contents' field in a signature -// dictionary will not cause an infinite loop in CPDF_SyntaxParser::GetObject(). -TEST_F(FPDFViewEmbeddertest, Hang_344) { - EXPECT_FALSE(OpenDocument("bug_344.pdf")); -} - -// The test should pass when there is no infinite recursion in -// CPDF_SyntaxParser::GetString(). -TEST_F(FPDFViewEmbeddertest, Hang_355) { - EXPECT_FALSE(OpenDocument("bug_355.pdf")); -} -// The test should pass even when the file has circular references to pages. -TEST_F(FPDFViewEmbeddertest, Hang_360) { - EXPECT_FALSE(OpenDocument("bug_360.pdf")); -} - -void FPDFViewEmbeddertest::TestRenderPageBitmapWithMatrix( - FPDF_PAGE page, - const int bitmap_width, - const int bitmap_height, - const FS_MATRIX& matrix, - const FS_RECTF& rect, - const char* expected_md5) { - FPDF_BITMAP bitmap = FPDFBitmap_Create(bitmap_width, bitmap_height, 0); - FPDFBitmap_FillRect(bitmap, 0, 0, bitmap_width, bitmap_height, 0xFFFFFFFF); - FPDF_RenderPageBitmapWithMatrix(bitmap, page, &matrix, &rect, 0); - CompareBitmap(bitmap, bitmap_width, bitmap_height, expected_md5); - FPDFBitmap_Destroy(bitmap); -} - -TEST_F(FPDFViewEmbeddertest, FPDF_RenderPageBitmapWithMatrix) { - const char kOriginalMD5[] = "0a90de37f52127619c3dfb642b5fa2fe"; - const char kClippedMD5[] = "a84cab93c102b9b9290fba3047ba702c"; - const char kTopLeftQuarterMD5[] = "f11a11137c8834389e31cf555a4a6979"; - const char kHoriStretchedMD5[] = "48ef9205941ed19691ccfa00d717187e"; - const char kRotated90ClockwiseMD5[] = "d8da2c7bf77521550d0f2752b9cf3482"; - const char kRotated180ClockwiseMD5[] = "0113386bb0bd45125bacc6dee78bfe78"; - const char kRotated270ClockwiseMD5[] = "a287e0f74ce203699cda89f9cc97a240"; - const char kMirrorHoriMD5[] = "6e8d7a6fde39d8e720fb9e620102918c"; - const char kMirrorVertMD5[] = "8f3a555ef9c0d5031831ae3715273707"; - const char kLargerTopLeftQuarterMD5[] = "172a2f4adafbadbe98017b1c025b9e27"; - const char kLargerMD5[] = "c806145641c3e6fc4e022c7065343749"; - const char kLargerClippedMD5[] = "091d3b1c7933c8f6945eb2cb41e588e9"; - const char kLargerRotatedMD5[] = "115f13353ebfc82ddb392d1f0059eb12"; - const char kLargerRotatedLandscapeMD5[] = "c901239d17d84ac84cb6f2124da71b0d"; - const char kLargerRotatedDiagonalMD5[] = "3d62417468bdaff0eb14391a0c30a3b1"; - const char kTileMD5[] = "0a190003c97220bf8877684c8d7e89cf"; - - EXPECT_TRUE(OpenDocument("rectangles.pdf")); - FPDF_PAGE page = LoadPage(0); - ASSERT_TRUE(page); - const int page_width = static_cast(FPDF_GetPageWidth(page)); - const int page_height = static_cast(FPDF_GetPageHeight(page)); - EXPECT_EQ(200, page_width); - EXPECT_EQ(300, page_height); - - std::unique_ptr bitmap = RenderLoadedPage(page); - CompareBitmap(bitmap.get(), page_width, page_height, kOriginalMD5); - - FS_RECTF page_rect{0, 0, page_width, page_height}; - - // Try rendering with an identity matrix. The output should be the same as - // the RenderLoadedPage() output. - FS_MATRIX identity_matrix{1, 0, 0, 1, 0, 0}; - TestRenderPageBitmapWithMatrix(page, page_width, page_height, identity_matrix, - page_rect, kOriginalMD5); - - // Again render with an identity matrix but with a smaller clipping rect. - FS_RECTF middle_of_page_rect{page_width / 4, page_height / 4, - page_width * 3 / 4, page_height * 3 / 4}; - TestRenderPageBitmapWithMatrix(page, page_width, page_height, identity_matrix, - middle_of_page_rect, kClippedMD5); - - // Now render again with the image scaled smaller. - FS_MATRIX half_scale_matrix{0.5, 0, 0, 0.5, 0, 0}; - TestRenderPageBitmapWithMatrix(page, page_width, page_height, - half_scale_matrix, page_rect, - kTopLeftQuarterMD5); - - // Now render again with the image scaled larger horizontally (the right half - // will be clipped). - FS_MATRIX stretch_x_matrix{2, 0, 0, 1, 0, 0}; - TestRenderPageBitmapWithMatrix(page, page_width, page_height, - stretch_x_matrix, page_rect, - kHoriStretchedMD5); - - // Try a 90 degree rotation clockwise but with the same bitmap size, so part - // will be clipped. - FS_MATRIX rotate_90_matrix{0, 1, -1, 0, page_width, 0}; - TestRenderPageBitmapWithMatrix(page, page_width, page_height, - rotate_90_matrix, page_rect, - kRotated90ClockwiseMD5); - - // 180 degree rotation clockwise. - FS_MATRIX rotate_180_matrix{-1, 0, 0, -1, page_width, page_height}; - TestRenderPageBitmapWithMatrix(page, page_width, page_height, - rotate_180_matrix, page_rect, - kRotated180ClockwiseMD5); - - // 270 degree rotation clockwise. - FS_MATRIX rotate_270_matrix{0, -1, 1, 0, 0, page_width}; - TestRenderPageBitmapWithMatrix(page, page_width, page_height, - rotate_270_matrix, page_rect, - kRotated270ClockwiseMD5); - - // Mirror horizontally. - FS_MATRIX mirror_hori_matrix{-1, 0, 0, 1, page_width, 0}; - TestRenderPageBitmapWithMatrix(page, page_width, page_height, - mirror_hori_matrix, page_rect, kMirrorHoriMD5); - - // Mirror vertically. - FS_MATRIX mirror_vert_matrix{1, 0, 0, -1, 0, page_height}; - TestRenderPageBitmapWithMatrix(page, page_width, page_height, - mirror_vert_matrix, page_rect, kMirrorVertMD5); - - // Tests rendering to a larger bitmap - const int bitmap_width = page_width * 2; - const int bitmap_height = page_height * 2; - - // Render using an identity matrix and the whole bitmap area as clipping rect. - FS_RECTF bitmap_rect{0, 0, bitmap_width, bitmap_height}; - TestRenderPageBitmapWithMatrix(page, bitmap_width, bitmap_height, - identity_matrix, bitmap_rect, - kLargerTopLeftQuarterMD5); - - // Render using a scaling matrix to fill the larger bitmap. - FS_MATRIX double_scale_matrix{2, 0, 0, 2, 0, 0}; - TestRenderPageBitmapWithMatrix(page, bitmap_width, bitmap_height, - double_scale_matrix, bitmap_rect, kLargerMD5); - - // Render the larger image again but with clipping. - FS_RECTF middle_of_bitmap_rect{bitmap_width / 4, bitmap_height / 4, - bitmap_width * 3 / 4, bitmap_height * 3 / 4}; - TestRenderPageBitmapWithMatrix(page, bitmap_width, bitmap_height, - double_scale_matrix, middle_of_bitmap_rect, - kLargerClippedMD5); - - // On the larger bitmap, try a 90 degree rotation but with the same bitmap - // size, so part will be clipped. - FS_MATRIX rotate_90_scale_2_matrix{0, 2, -2, 0, bitmap_width, 0}; - TestRenderPageBitmapWithMatrix(page, bitmap_width, bitmap_height, - rotate_90_scale_2_matrix, bitmap_rect, - kLargerRotatedMD5); - - // On the larger bitmap, apply 90 degree rotation to a bitmap with the - // appropriate dimensions. - const int landscape_bitmap_width = bitmap_height; - const int landscape_bitmap_height = bitmap_width; - FS_RECTF landscape_bitmap_rect{0, 0, landscape_bitmap_width, - landscape_bitmap_height}; - FS_MATRIX landscape_rotate_90_scale_2_matrix{ - 0, 2, -2, 0, landscape_bitmap_width, 0}; - TestRenderPageBitmapWithMatrix( - page, landscape_bitmap_width, landscape_bitmap_height, - landscape_rotate_90_scale_2_matrix, landscape_bitmap_rect, - kLargerRotatedLandscapeMD5); - - // On the larger bitmap, apply 45 degree rotation to a bitmap with the - // appropriate dimensions. - const float sqrt2 = 1.41421356f; - const int diagonal_bitmap_size = ceil((bitmap_width + bitmap_height) / sqrt2); - FS_RECTF diagonal_bitmap_rect{0, 0, diagonal_bitmap_size, - diagonal_bitmap_size}; - FS_MATRIX rotate_45_scale_2_matrix{ - sqrt2, sqrt2, -sqrt2, sqrt2, bitmap_height / sqrt2, 0}; - TestRenderPageBitmapWithMatrix(page, diagonal_bitmap_size, - diagonal_bitmap_size, rotate_45_scale_2_matrix, - diagonal_bitmap_rect, - kLargerRotatedDiagonalMD5); - - // Render the (2, 1) tile of the page (third column, second row) when the page - // is divided in 50x50 pixel tiles. The tile is scaled by a factor of 7. - const float scale = 7.0; - const int tile_size = 50; - const int tile_x = 2; - const int tile_y = 1; - int tile_bitmap_size = scale * tile_size; - FS_RECTF tile_bitmap_rect{0, 0, tile_bitmap_size, tile_bitmap_size}; - FS_MATRIX tile_2_1_matrix{scale, - 0, - 0, - scale, - -tile_x * tile_bitmap_size, - -tile_y * tile_bitmap_size}; - TestRenderPageBitmapWithMatrix(page, tile_bitmap_size, tile_bitmap_size, - tile_2_1_matrix, tile_bitmap_rect, kTileMD5); - - UnloadPage(page); -} - -class UnSupRecordDelegate : public EmbedderTest::Delegate { - public: - UnSupRecordDelegate() : type_(-1) {} - ~UnSupRecordDelegate() override {} - - void UnsupportedHandler(int type) override { type_ = type; } - - int type_; -}; - -TEST_F(FPDFViewEmbeddertest, UnSupportedOperations_NotFound) { - UnSupRecordDelegate delegate; - SetDelegate(&delegate); - ASSERT_TRUE(OpenDocument("hello_world.pdf")); - EXPECT_EQ(delegate.type_, -1); - SetDelegate(nullptr); -} - -TEST_F(FPDFViewEmbeddertest, UnSupportedOperations_LoadCustomDocument) { - UnSupRecordDelegate delegate; - SetDelegate(&delegate); - ASSERT_TRUE(OpenDocument("unsupported_feature.pdf")); - EXPECT_EQ(FPDF_UNSP_DOC_PORTABLECOLLECTION, delegate.type_); - SetDelegate(nullptr); -} - -TEST_F(FPDFViewEmbeddertest, UnSupportedOperations_LoadDocument) { - std::string file_path; - ASSERT_TRUE( - PathService::GetTestFilePath("unsupported_feature.pdf", &file_path)); - - UnSupRecordDelegate delegate; - SetDelegate(&delegate); - FPDF_DOCUMENT doc = FPDF_LoadDocument(file_path.c_str(), ""); - EXPECT_TRUE(doc != nullptr); - EXPECT_EQ(FPDF_UNSP_DOC_PORTABLECOLLECTION, delegate.type_); - FPDF_CloseDocument(doc); - SetDelegate(nullptr); -} -- cgit v1.2.3