1 // Copyright 2019 PDFium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 #include "public/fpdf_annot.h"
6 
7 #include <vector>
8 
9 #include "constants/annotation_common.h"
10 #include "core/fpdfapi/page/cpdf_annotcontext.h"
11 #include "core/fpdfapi/page/cpdf_pagemodule.h"
12 #include "core/fpdfapi/parser/cpdf_dictionary.h"
13 #include "fpdfsdk/cpdfsdk_helpers.h"
14 #include "public/cpp/fpdf_scopers.h"
15 #include "public/fpdf_edit.h"
16 #include "testing/fx_string_testhelpers.h"
17 #include "testing/gtest/include/gtest/gtest.h"
18 
19 namespace {
20 
21 const wchar_t kStreamData[] =
22     L"/GS gs 0.0 0.0 0.0 RG 4 w 211.8 747.6 m 211.8 744.8 "
23     L"212.6 743.0 214.2 740.8 "
24     L"c 215.4 739.0 216.8 737.1 218.9 736.1 c 220.8 735.1 221.4 733.0 "
25     L"223.7 732.4 c 232.6 729.9 242.0 730.8 251.2 730.8 c 257.5 730.8 "
26     L"263.0 732.9 269.0 734.4 c S";
27 
28 }  // namespace
29 
30 class PDFAnnotTest : public testing::Test {
31  protected:
32   PDFAnnotTest() = default;
33   ~PDFAnnotTest() override = default;
34 
SetUp()35   void SetUp() override { CPDF_PageModule::Create(); }
TearDown()36   void TearDown() override { CPDF_PageModule::Destroy(); }
37 };
38 
TEST_F(PDFAnnotTest,SetAP)39 TEST_F(PDFAnnotTest, SetAP) {
40   ScopedFPDFDocument doc(FPDF_CreateNewDocument());
41   ASSERT_TRUE(doc);
42   ScopedFPDFPage page(FPDFPage_New(doc.get(), 0, 100, 100));
43   ASSERT_TRUE(page);
44   ScopedFPDFWideString ap_stream = GetFPDFWideString(kStreamData);
45   ASSERT_TRUE(ap_stream);
46 
47   ScopedFPDFAnnotation annot(FPDFPage_CreateAnnot(page.get(), FPDF_ANNOT_INK));
48   ASSERT_TRUE(annot);
49 
50   // Negative case: FPDFAnnot_SetAP() should fail if bounding rect is not yet
51   // set on the annotation.
52   EXPECT_FALSE(FPDFAnnot_SetAP(annot.get(), FPDF_ANNOT_APPEARANCEMODE_NORMAL,
53                                ap_stream.get()));
54 
55   const FS_RECTF bounding_rect{206.0f, 753.0f, 339.0f, 709.0f};
56   EXPECT_TRUE(FPDFAnnot_SetRect(annot.get(), &bounding_rect));
57 
58   ASSERT_TRUE(FPDFAnnot_SetColor(annot.get(), FPDFANNOT_COLORTYPE_Color,
59                                  /*R=*/255, /*G=*/0, /*B=*/0, /*A=*/255));
60 
61   EXPECT_TRUE(FPDFAnnot_SetAP(annot.get(), FPDF_ANNOT_APPEARANCEMODE_NORMAL,
62                               ap_stream.get()));
63 
64   // Verify that appearance stream is created as form XObject
65   CPDF_AnnotContext* context = CPDFAnnotContextFromFPDFAnnotation(annot.get());
66   ASSERT_TRUE(context);
67   CPDF_Dictionary* annot_dict = context->GetAnnotDict();
68   ASSERT_TRUE(annot_dict);
69   CPDF_Dictionary* ap_dict = annot_dict->GetDictFor(pdfium::annotation::kAP);
70   ASSERT_TRUE(ap_dict);
71   CPDF_Dictionary* stream_dict = ap_dict->GetDictFor("N");
72   ASSERT_TRUE(stream_dict);
73   // Check for non-existence of resources dictionary in case of opaque color
74   CPDF_Dictionary* resources_dict = stream_dict->GetDictFor("Resources");
75   ASSERT_FALSE(resources_dict);
76   ByteString type = stream_dict->GetStringFor(pdfium::annotation::kType);
77   EXPECT_EQ("XObject", type);
78   ByteString sub_type = stream_dict->GetStringFor(pdfium::annotation::kSubtype);
79   EXPECT_EQ("Form", sub_type);
80 
81   // Check that the appearance stream is same as we just set.
82   const uint32_t kStreamDataSize =
83       FX_ArraySize(kStreamData) * sizeof(FPDF_WCHAR);
84   unsigned long normal_length_bytes = FPDFAnnot_GetAP(
85       annot.get(), FPDF_ANNOT_APPEARANCEMODE_NORMAL, nullptr, 0);
86   ASSERT_EQ(kStreamDataSize, normal_length_bytes);
87   std::vector<FPDF_WCHAR> buf = GetFPDFWideStringBuffer(normal_length_bytes);
88   EXPECT_EQ(kStreamDataSize,
89             FPDFAnnot_GetAP(annot.get(), FPDF_ANNOT_APPEARANCEMODE_NORMAL,
90                             buf.data(), normal_length_bytes));
91   EXPECT_EQ(kStreamData, GetPlatformWString(buf.data()));
92 }
93 
TEST_F(PDFAnnotTest,SetAPWithOpacity)94 TEST_F(PDFAnnotTest, SetAPWithOpacity) {
95   ScopedFPDFDocument doc(FPDF_CreateNewDocument());
96   ASSERT_TRUE(doc);
97   ScopedFPDFPage page(FPDFPage_New(doc.get(), 0, 100, 100));
98   ASSERT_TRUE(page);
99   ScopedFPDFWideString ap_stream = GetFPDFWideString(kStreamData);
100   ASSERT_TRUE(ap_stream);
101 
102   ScopedFPDFAnnotation annot(FPDFPage_CreateAnnot(page.get(), FPDF_ANNOT_INK));
103   ASSERT_TRUE(annot);
104 
105   ASSERT_TRUE(FPDFAnnot_SetColor(annot.get(), FPDFANNOT_COLORTYPE_Color,
106                                  /*R=*/255, /*G=*/0, /*B=*/0, /*A=*/102));
107 
108   const FS_RECTF bounding_rect{206.0f, 753.0f, 339.0f, 709.0f};
109   EXPECT_TRUE(FPDFAnnot_SetRect(annot.get(), &bounding_rect));
110 
111   EXPECT_TRUE(FPDFAnnot_SetAP(annot.get(), FPDF_ANNOT_APPEARANCEMODE_NORMAL,
112                               ap_stream.get()));
113 
114   CPDF_AnnotContext* context = CPDFAnnotContextFromFPDFAnnotation(annot.get());
115   ASSERT_TRUE(context);
116   CPDF_Dictionary* annot_dict = context->GetAnnotDict();
117   ASSERT_TRUE(annot_dict);
118   CPDF_Dictionary* ap_dict = annot_dict->GetDictFor(pdfium::annotation::kAP);
119   ASSERT_TRUE(ap_dict);
120   CPDF_Dictionary* stream_dict = ap_dict->GetDictFor("N");
121   ASSERT_TRUE(stream_dict);
122   CPDF_Dictionary* resources_dict = stream_dict->GetDictFor("Resources");
123   ASSERT_TRUE(stream_dict);
124   CPDF_Dictionary* extGState_dict = resources_dict->GetDictFor("ExtGState");
125   ASSERT_TRUE(extGState_dict);
126   CPDF_Dictionary* gs_dict = extGState_dict->GetDictFor("GS");
127   ASSERT_TRUE(gs_dict);
128   ByteString type = gs_dict->GetStringFor(pdfium::annotation::kType);
129   EXPECT_EQ("ExtGState", type);
130   float opacity = gs_dict->GetNumberFor("CA");
131   // Opacity value of 102 is represented as 0.4f (=104/255) in /CA entry.
132   EXPECT_FLOAT_EQ(0.4f, opacity);
133   ByteString blend_mode = gs_dict->GetStringFor("BM");
134   EXPECT_EQ("Normal", blend_mode);
135   bool alpha_source_flag = gs_dict->GetBooleanFor("AIS", true);
136   EXPECT_FALSE(alpha_source_flag);
137 }
138