Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
cpdf_annot.cpp
Go to the documentation of this file.
1// Copyright 2016 The PDFium Authors
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5// Original code copyright 2014 Foxit Software Inc. http://www.foxitsoftware.com
6
7#include "core/fpdfdoc/cpdf_annot.h"
8
9#include <algorithm>
10#include <utility>
11
12#include "build/build_config.h"
13#include "constants/annotation_common.h"
14#include "constants/annotation_flags.h"
15#include "core/fpdfapi/page/cpdf_form.h"
16#include "core/fpdfapi/page/cpdf_page.h"
17#include "core/fpdfapi/page/cpdf_pageimagecache.h"
18#include "core/fpdfapi/parser/cpdf_array.h"
19#include "core/fpdfapi/parser/cpdf_boolean.h"
20#include "core/fpdfapi/parser/cpdf_dictionary.h"
21#include "core/fpdfapi/parser/cpdf_document.h"
22#include "core/fpdfapi/parser/cpdf_stream.h"
23#include "core/fpdfapi/parser/fpdf_parser_utility.h"
24#include "core/fpdfapi/render/cpdf_rendercontext.h"
25#include "core/fpdfapi/render/cpdf_renderoptions.h"
26#include "core/fpdfdoc/cpdf_generateap.h"
27#include "core/fxcrt/check.h"
28#include "core/fxge/cfx_fillrenderoptions.h"
29#include "core/fxge/cfx_graphstatedata.h"
30#include "core/fxge/cfx_path.h"
31#include "core/fxge/cfx_renderdevice.h"
32
33namespace {
34
35const char kPDFiumKey_HasGeneratedAP[] = "PDFIUM_HasGeneratedAP";
36
37bool IsTextMarkupAnnotation(CPDF_Annot::Subtype type) {
38 return type == CPDF_Annot::Subtype::HIGHLIGHT ||
42}
43
44CPDF_Form* AnnotGetMatrix(CPDF_Page* pPage,
45 CPDF_Annot* pAnnot,
47 const CFX_Matrix& mtUser2Device,
48 CFX_Matrix* matrix) {
49 CPDF_Form* pForm = pAnnot->GetAPForm(pPage, mode);
50 if (!pForm)
51 return nullptr;
52
53 CFX_Matrix form_matrix = pForm->GetDict()->GetMatrixFor("Matrix");
54 CFX_FloatRect form_bbox =
55 form_matrix.TransformRect(pForm->GetDict()->GetRectFor("BBox"));
56 matrix->MatchRect(pAnnot->GetRect(), form_bbox);
57
58 // Compensate for page rotation.
60 pPage->GetPageRotation() != 0) {
61 // Rotate annotation rect around top-left angle (according to the
62 // specification).
63 const float offset_x = pAnnot->GetRect().Left();
64 const float offset_y = pAnnot->GetRect().Top();
65 matrix->Concat({1, 0, 0, 1, -offset_x, -offset_y});
66 // GetPageRotation returns value in fractions of pi/2.
67 const float angle = FXSYS_PI / 2 * pPage->GetPageRotation();
68 matrix->Rotate(angle);
69 matrix->Concat({1, 0, 0, 1, offset_x, offset_y});
70 }
71
72 matrix->Concat(mtUser2Device);
73 return pForm;
74}
75
76RetainPtr<CPDF_Stream> GetAnnotAPInternal(CPDF_Dictionary* pAnnotDict,
78 bool bFallbackToNormal) {
80 pAnnotDict->GetMutableDictFor(pdfium::annotation::kAP);
81 if (!pAP)
82 return nullptr;
83
84 const char* ap_entry = "N";
86 ap_entry = "D";
88 ap_entry = "R";
89 if (bFallbackToNormal && !pAP->KeyExist(ap_entry))
90 ap_entry = "N";
91
92 RetainPtr<CPDF_Object> psub = pAP->GetMutableDirectObjectFor(ap_entry);
93 if (!psub)
94 return nullptr;
95
96 RetainPtr<CPDF_Stream> pStream(psub->AsMutableStream());
97 if (pStream)
98 return pStream;
99
100 CPDF_Dictionary* pDict = psub->AsMutableDictionary();
101 if (!pDict)
102 return nullptr;
103
105 if (as.IsEmpty()) {
106 ByteString value = pAnnotDict->GetByteStringFor("V");
107 if (value.IsEmpty()) {
108 RetainPtr<const CPDF_Dictionary> pParentDict =
109 pAnnotDict->GetDictFor("Parent");
110 value = pParentDict ? pParentDict->GetByteStringFor("V") : ByteString();
111 }
112 as = (!value.IsEmpty() && pDict->KeyExist(value)) ? value : "Off";
113 }
114 return pDict->GetMutableStreamFor(as);
115}
116
117} // namespace
118
120 CPDF_Document* pDocument)
125 m_bIsTextMarkupAnnotation(IsTextMarkupAnnotation(m_nSubtype)),
128 GenerateAPIfNeeded();
129}
130
134
135void CPDF_Annot::GenerateAPIfNeeded() {
136 if (!ShouldGenerateAP())
137 return;
138 if (!CPDF_GenerateAP::GenerateAnnotAP(m_pDocument, m_pAnnotDict.Get(),
139 m_nSubtype)) {
140 return;
141 }
142
143 m_pAnnotDict->SetNewFor<CPDF_Boolean>(kPDFiumKey_HasGeneratedAP, true);
144 m_bHasGeneratedAP = true;
145}
146
147bool CPDF_Annot::ShouldGenerateAP() const {
148 // If AP dictionary exists and defines an appearance for normal mode, we use
149 // the appearance defined in the existing AP dictionary.
150 RetainPtr<const CPDF_Dictionary> pAP =
151 m_pAnnotDict->GetDictFor(pdfium::annotation::kAP);
152 if (pAP && pAP->GetDictFor("N"))
153 return false;
154
155 return !IsHidden();
156}
157
158bool CPDF_Annot::ShouldDrawAnnotation() const {
159 if (IsHidden())
160 return false;
161 return m_bOpenState || m_nSubtype != CPDF_Annot::Subtype::POPUP;
162}
163
165 m_APMap.clear();
166}
167
169 return m_nSubtype;
170}
171
172CFX_FloatRect CPDF_Annot::RectForDrawing() const {
173 bool bShouldUseQuadPointsCoords =
174 m_bIsTextMarkupAnnotation && m_bHasGeneratedAP;
175 if (bShouldUseQuadPointsCoords)
176 return BoundingRectFromQuadPoints(m_pAnnotDict.Get());
177 return m_pAnnotDict->GetRectFor(pdfium::annotation::kRect);
178}
179
181 CFX_FloatRect rect = RectForDrawing();
182 rect.Normalize();
183 return rect;
184}
185
186uint32_t CPDF_Annot::GetFlags() const {
187 return m_pAnnotDict->GetIntegerFor(pdfium::annotation::kF);
188}
189
193
194RetainPtr<CPDF_Stream> GetAnnotAP(CPDF_Dictionary* pAnnotDict,
195 CPDF_Annot::AppearanceMode eMode) {
196 DCHECK(pAnnotDict);
197 return GetAnnotAPInternal(pAnnotDict, eMode, true);
198}
199
201 CPDF_Annot::AppearanceMode eMode) {
202 DCHECK(pAnnotDict);
203 return GetAnnotAPInternal(pAnnotDict, eMode, false);
204}
205
206CPDF_Form* CPDF_Annot::GetAPForm(CPDF_Page* pPage, AppearanceMode mode) {
207 RetainPtr<CPDF_Stream> pStream = GetAnnotAP(m_pAnnotDict.Get(), mode);
208 if (!pStream)
209 return nullptr;
210
211 auto it = m_APMap.find(pStream);
212 if (it != m_APMap.end())
213 return it->second.get();
214
215 auto pNewForm = std::make_unique<CPDF_Form>(
216 m_pDocument, pPage->GetMutableResources(), pStream);
217 pNewForm->ParseContent();
218
219 CPDF_Form* pResult = pNewForm.get();
220 m_APMap[pStream] = std::move(pNewForm);
221 return pResult;
222}
223
224void CPDF_Annot::SetPopupAnnotOpenState(bool bOpenState) {
225 if (m_pPopupAnnot)
226 m_pPopupAnnot->SetOpenState(bOpenState);
227}
228
230 if (!m_pPopupAnnot)
231 return std::nullopt;
232 return m_pPopupAnnot->GetRect();
233}
234
235// static
236CFX_FloatRect CPDF_Annot::RectFromQuadPointsArray(const CPDF_Array* pArray,
237 size_t nIndex) {
238 DCHECK(pArray);
239 DCHECK(nIndex < pArray->size() / 8);
240
241 // QuadPoints are defined with 4 pairs of numbers
242 // ([ pair0, pair1, pair2, pair3 ]), where
243 // pair0 = top_left
244 // pair1 = top_right
245 // pair2 = bottom_left
246 // pair3 = bottom_right
247 //
248 // On the other hand, /Rect is defined as 2 pairs [pair0, pair1] where:
249 // pair0 = bottom_left
250 // pair1 = top_right.
251
252 return CFX_FloatRect(
253 pArray->GetFloatAt(4 + nIndex * 8), pArray->GetFloatAt(5 + nIndex * 8),
254 pArray->GetFloatAt(2 + nIndex * 8), pArray->GetFloatAt(3 + nIndex * 8));
255}
256
257// static
259 const CPDF_Dictionary* pAnnotDict) {
260 CFX_FloatRect ret;
261 RetainPtr<const CPDF_Array> pArray = pAnnotDict->GetArrayFor("QuadPoints");
262 size_t nQuadPointCount = pArray ? QuadPointCount(pArray.Get()) : 0;
263 if (nQuadPointCount == 0)
264 return ret;
265
266 ret = RectFromQuadPointsArray(pArray.Get(), 0);
267 for (size_t i = 1; i < nQuadPointCount; ++i) {
268 CFX_FloatRect rect = RectFromQuadPointsArray(pArray.Get(), i);
269 ret.Union(rect);
270 }
271 return ret;
272}
273
274// static
275CFX_FloatRect CPDF_Annot::RectFromQuadPoints(const CPDF_Dictionary* pAnnotDict,
276 size_t nIndex) {
277 RetainPtr<const CPDF_Array> pArray = pAnnotDict->GetArrayFor("QuadPoints");
278 size_t nQuadPointCount = pArray ? QuadPointCount(pArray.Get()) : 0;
279 if (nIndex >= nQuadPointCount)
280 return CFX_FloatRect();
281 return RectFromQuadPointsArray(pArray.Get(), nIndex);
282}
283
284// static
286 const ByteString& sSubtype) {
287 if (sSubtype == "Text")
289 if (sSubtype == "Link")
291 if (sSubtype == "FreeText")
293 if (sSubtype == "Line")
295 if (sSubtype == "Square")
297 if (sSubtype == "Circle")
299 if (sSubtype == "Polygon")
301 if (sSubtype == "PolyLine")
303 if (sSubtype == "Highlight")
305 if (sSubtype == "Underline")
307 if (sSubtype == "Squiggly")
309 if (sSubtype == "StrikeOut")
311 if (sSubtype == "Stamp")
313 if (sSubtype == "Caret")
315 if (sSubtype == "Ink")
317 if (sSubtype == "Popup")
319 if (sSubtype == "FileAttachment")
321 if (sSubtype == "Sound")
323 if (sSubtype == "Movie")
325 if (sSubtype == "Widget")
327 if (sSubtype == "Screen")
329 if (sSubtype == "PrinterMark")
331 if (sSubtype == "TrapNet")
333 if (sSubtype == "Watermark")
335 if (sSubtype == "3D")
337 if (sSubtype == "RichMedia")
339 if (sSubtype == "XFAWidget")
341 if (sSubtype == "Redact")
344}
345
346// static
348 if (nSubtype == CPDF_Annot::Subtype::TEXT)
349 return "Text";
350 if (nSubtype == CPDF_Annot::Subtype::LINK)
351 return "Link";
352 if (nSubtype == CPDF_Annot::Subtype::FREETEXT)
353 return "FreeText";
354 if (nSubtype == CPDF_Annot::Subtype::LINE)
355 return "Line";
356 if (nSubtype == CPDF_Annot::Subtype::SQUARE)
357 return "Square";
358 if (nSubtype == CPDF_Annot::Subtype::CIRCLE)
359 return "Circle";
360 if (nSubtype == CPDF_Annot::Subtype::POLYGON)
361 return "Polygon";
362 if (nSubtype == CPDF_Annot::Subtype::POLYLINE)
363 return "PolyLine";
364 if (nSubtype == CPDF_Annot::Subtype::HIGHLIGHT)
365 return "Highlight";
366 if (nSubtype == CPDF_Annot::Subtype::UNDERLINE)
367 return "Underline";
368 if (nSubtype == CPDF_Annot::Subtype::SQUIGGLY)
369 return "Squiggly";
370 if (nSubtype == CPDF_Annot::Subtype::STRIKEOUT)
371 return "StrikeOut";
372 if (nSubtype == CPDF_Annot::Subtype::STAMP)
373 return "Stamp";
374 if (nSubtype == CPDF_Annot::Subtype::CARET)
375 return "Caret";
376 if (nSubtype == CPDF_Annot::Subtype::INK)
377 return "Ink";
378 if (nSubtype == CPDF_Annot::Subtype::POPUP)
379 return "Popup";
381 return "FileAttachment";
382 if (nSubtype == CPDF_Annot::Subtype::SOUND)
383 return "Sound";
384 if (nSubtype == CPDF_Annot::Subtype::MOVIE)
385 return "Movie";
386 if (nSubtype == CPDF_Annot::Subtype::WIDGET)
387 return "Widget";
388 if (nSubtype == CPDF_Annot::Subtype::SCREEN)
389 return "Screen";
390 if (nSubtype == CPDF_Annot::Subtype::PRINTERMARK)
391 return "PrinterMark";
392 if (nSubtype == CPDF_Annot::Subtype::TRAPNET)
393 return "TrapNet";
394 if (nSubtype == CPDF_Annot::Subtype::WATERMARK)
395 return "Watermark";
396 if (nSubtype == CPDF_Annot::Subtype::THREED)
397 return "3D";
398 if (nSubtype == CPDF_Annot::Subtype::RICHMEDIA)
399 return "RichMedia";
400 if (nSubtype == CPDF_Annot::Subtype::XFAWIDGET)
401 return "XFAWidget";
402 if (nSubtype == CPDF_Annot::Subtype::REDACT)
403 return "Redact";
404 return ByteString();
405}
406
407// static
408size_t CPDF_Annot::QuadPointCount(const CPDF_Array* pArray) {
409 return pArray->size() / 8;
410}
411
412bool CPDF_Annot::DrawAppearance(CPDF_Page* pPage,
413 CFX_RenderDevice* pDevice,
414 const CFX_Matrix& mtUser2Device,
415 AppearanceMode mode) {
416 if (!ShouldDrawAnnotation())
417 return false;
418
419 // It might happen that by the time this annotation instance was created,
420 // it was flagged as "hidden" (e.g. /F 2), and hence CPDF_GenerateAP decided
421 // to not "generate" its AP.
422 // If for a reason the object is no longer hidden, but still does not have
423 // its "AP" generated, generate it now.
424 GenerateAPIfNeeded();
425
426 CFX_Matrix matrix;
427 CPDF_Form* pForm = AnnotGetMatrix(pPage, this, mode, mtUser2Device, &matrix);
428 if (!pForm)
429 return false;
430
432 pPage->GetMutablePageResources(),
434 context.AppendLayer(pForm, matrix);
435 context.Render(pDevice, nullptr, nullptr, nullptr);
436 return true;
437}
438
439bool CPDF_Annot::DrawInContext(CPDF_Page* pPage,
440 CPDF_RenderContext* pContext,
441 const CFX_Matrix& mtUser2Device,
442 AppearanceMode mode) {
443 if (!ShouldDrawAnnotation())
444 return false;
445
446 // It might happen that by the time this annotation instance was created,
447 // it was flagged as "hidden" (e.g. /F 2), and hence CPDF_GenerateAP decided
448 // to not "generate" its AP.
449 // If for a reason the object is no longer hidden, but still does not have
450 // its "AP" generated, generate it now.
451 GenerateAPIfNeeded();
452
453 CFX_Matrix matrix;
454 CPDF_Form* pForm = AnnotGetMatrix(pPage, this, mode, mtUser2Device, &matrix);
455 if (!pForm)
456 return false;
457
458 pContext->AppendLayer(pForm, matrix);
459 return true;
460}
461
463 const CFX_Matrix* pUser2Device) {
465 return;
466
467 uint32_t annot_flags = GetFlags();
468 if (annot_flags & pdfium::annotation_flags::kHidden)
469 return;
470
471#if BUILDFLAG(IS_WIN)
472 bool is_printing = pDevice->GetDeviceType() == DeviceType::kPrinter;
473 if (is_printing && (annot_flags & pdfium::annotation_flags::kPrint) == 0) {
474 return;
475 }
476#else
477 const bool is_printing = false;
478#endif
479
480 if (!is_printing && (annot_flags & pdfium::annotation_flags::kNoView)) {
481 return;
482 }
483
484 RetainPtr<const CPDF_Dictionary> pBS = m_pAnnotDict->GetDictFor("BS");
485 char style_char;
486 float width;
487 RetainPtr<const CPDF_Array> pDashArray;
488 if (!pBS) {
489 RetainPtr<const CPDF_Array> pBorderArray =
490 m_pAnnotDict->GetArrayFor(pdfium::annotation::kBorder);
491 style_char = 'S';
492 if (pBorderArray) {
493 width = pBorderArray->GetFloatAt(2);
494 if (pBorderArray->size() == 4) {
495 pDashArray = pBorderArray->GetArrayAt(3);
496 if (!pDashArray) {
497 return;
498 }
499 size_t nLen = pDashArray->size();
500 size_t i = 0;
501 for (; i < nLen; ++i) {
502 RetainPtr<const CPDF_Object> pObj = pDashArray->GetDirectObjectAt(i);
503 if (pObj && pObj->GetInteger()) {
504 break;
505 }
506 }
507 if (i == nLen) {
508 return;
509 }
510 style_char = 'D';
511 }
512 } else {
513 width = 1;
514 }
515 } else {
516 ByteString style = pBS->GetByteStringFor("S");
517 pDashArray = pBS->GetArrayFor("D");
518 style_char = style[0];
519 width = pBS->GetFloatFor("W");
520 }
521 if (width <= 0) {
522 return;
523 }
524 RetainPtr<const CPDF_Array> pColor =
525 m_pAnnotDict->GetArrayFor(pdfium::annotation::kC);
526 uint32_t argb = 0xff000000;
527 if (pColor) {
528 int R = static_cast<int32_t>(pColor->GetFloatAt(0) * 255);
529 int G = static_cast<int32_t>(pColor->GetFloatAt(1) * 255);
530 int B = static_cast<int32_t>(pColor->GetFloatAt(2) * 255);
531 argb = ArgbEncode(0xff, R, G, B);
532 }
533 CFX_GraphStateData graph_state;
534 graph_state.m_LineWidth = width;
535 if (style_char == 'U') {
536 // TODO(https://crbug.com/237527): Handle the "Underline" border style
537 // instead of drawing the rectangle border.
538 return;
539 }
540
541 if (style_char == 'D') {
542 if (pDashArray) {
543 graph_state.m_DashArray =
544 ReadArrayElementsToVector(pDashArray.Get(), pDashArray->size());
545 if (graph_state.m_DashArray.size() % 2)
546 graph_state.m_DashArray.push_back(graph_state.m_DashArray.back());
547 } else {
548 graph_state.m_DashArray = {3.0f, 3.0f};
549 }
550 }
551
553 rect.Deflate(width / 2, width / 2);
554
555 CFX_Path path;
556 path.AppendFloatRect(rect);
557 pDevice->DrawPath(path, pUser2Device, &graph_state, argb, argb,
559}
fxcrt::ByteString ByteString
Definition bytestring.h:180
#define DCHECK
Definition check.h:33
float Left() const
constexpr CFX_FloatRect()=default
float Top() const
void Deflate(float x, float y)
void Union(const CFX_FloatRect &other_rect)
void MatchRect(const CFX_FloatRect &dest, const CFX_FloatRect &src)
void Rotate(float fRadian)
constexpr CFX_Matrix(float a1, float b1, float c1, float d1, float e1, float f1)
void Concat(const CFX_Matrix &right)
void AppendFloatRect(const CFX_FloatRect &rect)
Definition cfx_path.cpp:306
bool DrawPath(const CFX_Path &path, const CFX_Matrix *pObject2Device, const CFX_GraphStateData *pGraphState, uint32_t fill_color, uint32_t stroke_color, const CFX_FillRenderOptions &fill_options)
uint32_t GetFlags() const
CPDF_Form * GetAPForm(CPDF_Page *pPage, AppearanceMode mode)
bool DrawInContext(CPDF_Page *pPage, CPDF_RenderContext *pContext, const CFX_Matrix &mtUser2Device, AppearanceMode mode)
static ByteString AnnotSubtypeToString(Subtype nSubtype)
Subtype GetSubtype() const
void ClearCachedAP()
static Subtype StringToAnnotSubtype(const ByteString &sSubtype)
std::optional< CFX_FloatRect > GetPopupAnnotRect() const
CFX_FloatRect GetRect() const
CPDF_Annot(RetainPtr< CPDF_Dictionary > pDict, CPDF_Document *pDocument)
bool IsHidden() const
bool DrawAppearance(CPDF_Page *pPage, CFX_RenderDevice *pDevice, const CFX_Matrix &mtUser2Device, AppearanceMode mode)
void DrawBorder(CFX_RenderDevice *pDevice, const CFX_Matrix *pUser2Device)
static CFX_FloatRect BoundingRectFromQuadPoints(const CPDF_Dictionary *pAnnotDict)
void SetPopupAnnotOpenState(bool bOpenState)
std::vector< RetainPtr< CPDF_Object > >::const_iterator const_iterator
Definition cpdf_array.h:29
bool KeyExist(const ByteString &key) const
RetainPtr< CPDF_Stream > GetMutableStreamFor(const ByteString &key)
ByteString GetByteStringFor(const ByteString &key) const
std::map< ByteString, RetainPtr< CPDF_Object >, std::less<> > DictMap
CPDF_Document * GetDocument() const override
Definition cpdf_page.cpp:51
int GetPageRotation() const
CPDF_PageImageCache * GetPageImageCache()
Definition cpdf_page.h:83
void AppendLayer(CPDF_PageObjectHolder *pObjectHolder, const CFX_Matrix &mtObject2Device)
void Render(CFX_RenderDevice *pDevice, const CPDF_PageObject *pStopObj, const CPDF_RenderOptions *pOptions, const CFX_Matrix *pLastMatrix)
ByteString()=default
bool operator==(const char *ptr) const
RetainPtr< CPDF_Stream > GetAnnotAPNoFallback(CPDF_Dictionary *pAnnotDict, CPDF_Annot::AppearanceMode eMode)
RetainPtr< CPDF_Stream > GetAnnotAP(CPDF_Dictionary *pAnnotDict, CPDF_Annot::AppearanceMode eMode)
constexpr FX_ARGB ArgbEncode(uint32_t a, uint32_t r, uint32_t g, uint32_t b)
Definition fx_dib.h:188
#define FXSYS_PI
Definition fx_system.h:44
constexpr uint32_t kNoView
constexpr uint32_t kHidden
constexpr uint32_t kNoRotate