1// Copyright 2016 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// Original code copyright 2014 Foxit Software Inc. http://www.foxitsoftware.com
6
7#include "xfa/fxfa/parser/cxfa_box.h"
8
9#include <algorithm>
10#include <utility>
11
12#include "fxjs/xfa/cjx_object.h"
13#include "xfa/fxfa/parser/cxfa_corner.h"
14#include "xfa/fxfa/parser/cxfa_edge.h"
15#include "xfa/fxfa/parser/cxfa_fill.h"
16#include "xfa/fxfa/parser/cxfa_margin.h"
17#include "xfa/fxfa/parser/cxfa_measurement.h"
18#include "xfa/fxfa/parser/cxfa_node.h"
19#include "xfa/fxfa/parser/cxfa_rectangle.h"
20#include "xfa/fxgraphics/cxfa_gepath.h"
21#include "xfa/fxgraphics/cxfa_gepattern.h"
22#include "xfa/fxgraphics/cxfa_geshading.h"
23#include "xfa/fxgraphics/cxfa_graphics.h"
24
25namespace {
26
27std::pair<XFA_AttributeEnum, CXFA_Stroke*> Style3D(
28    const std::vector<CXFA_Stroke*>& strokes) {
29  if (strokes.empty())
30    return {XFA_AttributeEnum::Unknown, nullptr};
31
32  CXFA_Stroke* stroke = strokes[0];
33  for (size_t i = 1; i < strokes.size(); i++) {
34    CXFA_Stroke* find = strokes[i];
35    if (!find)
36      continue;
37    if (!stroke)
38      stroke = find;
39    else if (stroke->GetStrokeType() != find->GetStrokeType())
40      stroke = find;
41    break;
42  }
43
44  XFA_AttributeEnum iType = stroke->GetStrokeType();
45  if (iType == XFA_AttributeEnum::Lowered ||
46      iType == XFA_AttributeEnum::Raised ||
47      iType == XFA_AttributeEnum::Etched ||
48      iType == XFA_AttributeEnum::Embossed) {
49    return {iType, stroke};
50  }
51  return {XFA_AttributeEnum::Unknown, stroke};
52}
53
54CXFA_Rectangle* ToRectangle(CXFA_Box* box) {
55  return static_cast<CXFA_Rectangle*>(box);
56}
57
58}  // namespace
59
60CXFA_Box::CXFA_Box(CXFA_Document* pDoc,
61                   XFA_PacketType ePacket,
62                   uint32_t validPackets,
63                   XFA_ObjectType oType,
64                   XFA_Element eType,
65                   const PropertyData* properties,
66                   const AttributeData* attributes,
67                   const WideStringView& elementName,
68                   std::unique_ptr<CJX_Object> js_node)
69    : CXFA_Node(pDoc,
70                ePacket,
71                validPackets,
72                oType,
73                eType,
74                properties,
75                attributes,
76                elementName,
77                std::move(js_node)) {}
78
79CXFA_Box::~CXFA_Box() = default;
80
81XFA_AttributeEnum CXFA_Box::GetHand() {
82  return JSObject()->GetEnum(XFA_Attribute::Hand);
83}
84
85XFA_AttributeEnum CXFA_Box::GetPresence() {
86  return JSObject()
87      ->TryEnum(XFA_Attribute::Presence, true)
88      .value_or(XFA_AttributeEnum::Visible);
89}
90
91int32_t CXFA_Box::CountEdges() {
92  return CountChildren(XFA_Element::Edge, false);
93}
94
95CXFA_Edge* CXFA_Box::GetEdgeIfExists(int32_t nIndex) {
96  if (nIndex == 0)
97    return JSObject()->GetOrCreateProperty<CXFA_Edge>(nIndex,
98                                                      XFA_Element::Edge);
99  return JSObject()->GetProperty<CXFA_Edge>(nIndex, XFA_Element::Edge);
100}
101
102std::vector<CXFA_Stroke*> CXFA_Box::GetStrokes() {
103  return GetStrokesInternal(false);
104}
105
106bool CXFA_Box::IsCircular() {
107  return JSObject()->GetBoolean(XFA_Attribute::Circular);
108}
109
110Optional<int32_t> CXFA_Box::GetStartAngle() {
111  return JSObject()->TryInteger(XFA_Attribute::StartAngle, false);
112}
113
114Optional<int32_t> CXFA_Box::GetSweepAngle() {
115  return JSObject()->TryInteger(XFA_Attribute::SweepAngle, false);
116}
117
118CXFA_Fill* CXFA_Box::GetOrCreateFillIfPossible() {
119  return JSObject()->GetOrCreateProperty<CXFA_Fill>(0, XFA_Element::Fill);
120}
121
122std::tuple<XFA_AttributeEnum, bool, float> CXFA_Box::Get3DStyle() {
123  if (GetElementType() == XFA_Element::Arc)
124    return {XFA_AttributeEnum::Unknown, false, 0.0f};
125
126  std::vector<CXFA_Stroke*> strokes = GetStrokesInternal(true);
127  CXFA_Stroke* stroke;
128  XFA_AttributeEnum iType;
129
130  std::tie(iType, stroke) = Style3D(strokes);
131  if (iType == XFA_AttributeEnum::Unknown)
132    return {XFA_AttributeEnum::Unknown, false, 0.0f};
133
134  return {iType, stroke->IsVisible(), stroke->GetThickness()};
135}
136
137std::vector<CXFA_Stroke*> CXFA_Box::GetStrokesInternal(bool bNull) {
138  std::vector<CXFA_Stroke*> strokes;
139  strokes.resize(8);
140
141  for (int32_t i = 0, j = 0; i < 4; i++) {
142    CXFA_Corner* corner;
143    if (i == 0) {
144      corner =
145          JSObject()->GetOrCreateProperty<CXFA_Corner>(i, XFA_Element::Corner);
146    } else {
147      corner = JSObject()->GetProperty<CXFA_Corner>(i, XFA_Element::Corner);
148    }
149
150    // TODO(dsinclair): If i == 0 and GetOrCreateProperty failed, we can end up
151    // with a null corner in the first position.
152    if (corner || i == 0) {
153      strokes[j] = corner;
154    } else if (!bNull) {
155      if (i == 1 || i == 2)
156        strokes[j] = strokes[0];
157      else
158        strokes[j] = strokes[2];
159    }
160    j++;
161
162    CXFA_Edge* edge;
163    if (i == 0)
164      edge = JSObject()->GetOrCreateProperty<CXFA_Edge>(i, XFA_Element::Edge);
165    else
166      edge = JSObject()->GetProperty<CXFA_Edge>(i, XFA_Element::Edge);
167
168    // TODO(dsinclair): If i == 0 and GetOrCreateProperty failed, we can end up
169    // with a null edge in the first position.
170    if (edge || i == 0) {
171      strokes[j] = edge;
172    } else if (!bNull) {
173      if (i == 1 || i == 2)
174        strokes[j] = strokes[1];
175      else
176        strokes[j] = strokes[3];
177    }
178    j++;
179  }
180  return strokes;
181}
182
183void CXFA_Box::Draw(CXFA_Graphics* pGS,
184                    const CFX_RectF& rtWidget,
185                    const CFX_Matrix& matrix,
186                    bool forceRound) {
187  if (GetPresence() != XFA_AttributeEnum::Visible)
188    return;
189
190  XFA_Element eType = GetElementType();
191  if (eType != XFA_Element::Arc && eType != XFA_Element::Border &&
192      eType != XFA_Element::Rectangle) {
193    return;
194  }
195  std::vector<CXFA_Stroke*> strokes;
196  if (!forceRound && eType != XFA_Element::Arc)
197    strokes = GetStrokes();
198
199  DrawFill(strokes, pGS, rtWidget, matrix, forceRound);
200  XFA_Element type = GetElementType();
201  if (type == XFA_Element::Arc || forceRound) {
202    StrokeArcOrRounded(pGS, rtWidget, matrix, forceRound);
203  } else if (type == XFA_Element::Rectangle || type == XFA_Element::Border) {
204    ToRectangle(this)->Draw(strokes, pGS, rtWidget, matrix);
205  } else {
206    NOTREACHED();
207  }
208}
209
210void CXFA_Box::DrawFill(const std::vector<CXFA_Stroke*>& strokes,
211                        CXFA_Graphics* pGS,
212                        CFX_RectF rtWidget,
213                        const CFX_Matrix& matrix,
214                        bool forceRound) {
215  CXFA_Fill* fill = JSObject()->GetProperty<CXFA_Fill>(0, XFA_Element::Fill);
216  if (!fill || !fill->IsVisible())
217    return;
218
219  pGS->SaveGraphState();
220
221  CXFA_GEPath fillPath;
222  XFA_Element type = GetElementType();
223  if (type == XFA_Element::Arc || forceRound) {
224    CXFA_Edge* edge = GetEdgeIfExists(0);
225    float fThickness = std::fmax(0.0, edge ? edge->GetThickness() : 0);
226    float fHalf = fThickness / 2;
227    XFA_AttributeEnum iHand = GetHand();
228    if (iHand == XFA_AttributeEnum::Left)
229      rtWidget.Inflate(fHalf, fHalf);
230    else if (iHand == XFA_AttributeEnum::Right)
231      rtWidget.Deflate(fHalf, fHalf);
232
233    GetPathArcOrRounded(rtWidget, fillPath, forceRound);
234  } else if (type == XFA_Element::Rectangle || type == XFA_Element::Border) {
235    ToRectangle(this)->GetFillPath(strokes, rtWidget, &fillPath);
236  } else {
237    NOTREACHED();
238  }
239  fillPath.Close();
240
241  fill->Draw(pGS, &fillPath, rtWidget, matrix);
242  pGS->RestoreGraphState();
243}
244
245void CXFA_Box::GetPathArcOrRounded(CFX_RectF rtDraw,
246                                   CXFA_GEPath& fillPath,
247                                   bool forceRound) {
248  float a, b;
249  a = rtDraw.width / 2.0f;
250  b = rtDraw.height / 2.0f;
251  if (IsCircular() || forceRound)
252    a = b = std::min(a, b);
253
254  CFX_PointF center = rtDraw.Center();
255  rtDraw.left = center.x - a;
256  rtDraw.top = center.y - b;
257  rtDraw.width = a + a;
258  rtDraw.height = b + b;
259  Optional<int32_t> startAngle = GetStartAngle();
260  Optional<int32_t> sweepAngle = GetSweepAngle();
261  if (!startAngle && !sweepAngle) {
262    fillPath.AddEllipse(rtDraw);
263    return;
264  }
265
266  fillPath.AddArc(rtDraw.TopLeft(), rtDraw.Size(),
267                  -startAngle.value_or(0) * FX_PI / 180.0f,
268                  -sweepAngle.value_or(360) * FX_PI / 180.0f);
269}
270
271void CXFA_Box::StrokeArcOrRounded(CXFA_Graphics* pGS,
272                                  CFX_RectF rtWidget,
273                                  const CFX_Matrix& matrix,
274                                  bool forceRound) {
275  CXFA_Edge* edge = GetEdgeIfExists(0);
276  if (!edge || !edge->IsVisible())
277    return;
278
279  bool bVisible;
280  float fThickness;
281  XFA_AttributeEnum i3DType;
282  std::tie(i3DType, bVisible, fThickness) = Get3DStyle();
283  bool lowered3d = false;
284  if (i3DType != XFA_AttributeEnum::Unknown) {
285    if (bVisible && fThickness >= 0.001f)
286      lowered3d = true;
287  }
288
289  float fHalf = edge->GetThickness() / 2;
290  if (fHalf < 0) {
291    fHalf = 0;
292  }
293
294  XFA_AttributeEnum iHand = GetHand();
295  if (iHand == XFA_AttributeEnum::Left) {
296    rtWidget.Inflate(fHalf, fHalf);
297  } else if (iHand == XFA_AttributeEnum::Right) {
298    rtWidget.Deflate(fHalf, fHalf);
299  }
300  if (!forceRound || !lowered3d) {
301    if (fHalf < 0.001f)
302      return;
303
304    CXFA_GEPath arcPath;
305    GetPathArcOrRounded(rtWidget, arcPath, forceRound);
306    if (edge)
307      edge->Stroke(&arcPath, pGS, matrix);
308    return;
309  }
310  pGS->SaveGraphState();
311  pGS->SetLineWidth(fHalf);
312
313  float a, b;
314  a = rtWidget.width / 2.0f;
315  b = rtWidget.height / 2.0f;
316  if (forceRound) {
317    a = std::min(a, b);
318    b = a;
319  }
320
321  CFX_PointF center = rtWidget.Center();
322  rtWidget.left = center.x - a;
323  rtWidget.top = center.y - b;
324  rtWidget.width = a + a;
325  rtWidget.height = b + b;
326
327  float startAngle = 0, sweepAngle = 360;
328  startAngle = startAngle * FX_PI / 180.0f;
329  sweepAngle = -sweepAngle * FX_PI / 180.0f;
330
331  CXFA_GEPath arcPath;
332  arcPath.AddArc(rtWidget.TopLeft(), rtWidget.Size(), 3.0f * FX_PI / 4.0f,
333                 FX_PI);
334
335  pGS->SetStrokeColor(CXFA_GEColor(0xFF808080));
336  pGS->StrokePath(&arcPath, &matrix);
337  arcPath.Clear();
338  arcPath.AddArc(rtWidget.TopLeft(), rtWidget.Size(), -1.0f * FX_PI / 4.0f,
339                 FX_PI);
340
341  pGS->SetStrokeColor(CXFA_GEColor(0xFFFFFFFF));
342  pGS->StrokePath(&arcPath, &matrix);
343  rtWidget.Deflate(fHalf, fHalf);
344  arcPath.Clear();
345  arcPath.AddArc(rtWidget.TopLeft(), rtWidget.Size(), 3.0f * FX_PI / 4.0f,
346                 FX_PI);
347
348  pGS->SetStrokeColor(CXFA_GEColor(0xFF404040));
349  pGS->StrokePath(&arcPath, &matrix);
350  arcPath.Clear();
351  arcPath.AddArc(rtWidget.TopLeft(), rtWidget.Size(), -1.0f * FX_PI / 4.0f,
352                 FX_PI);
353
354  pGS->SetStrokeColor(CXFA_GEColor(0xFFC0C0C0));
355  pGS->StrokePath(&arcPath, &matrix);
356  pGS->RestoreGraphState();
357}
358