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