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 <cmath>
11 #include <utility>
12 
13 #include "fxjs/xfa/cjx_object.h"
14 #include "third_party/base/notreached.h"
15 #include "xfa/fgas/graphics/cfgas_gegraphics.h"
16 #include "xfa/fgas/graphics/cfgas_gepath.h"
17 #include "xfa/fgas/graphics/cfgas_gepattern.h"
18 #include "xfa/fgas/graphics/cfgas_geshading.h"
19 #include "xfa/fxfa/parser/cxfa_corner.h"
20 #include "xfa/fxfa/parser/cxfa_edge.h"
21 #include "xfa/fxfa/parser/cxfa_fill.h"
22 #include "xfa/fxfa/parser/cxfa_margin.h"
23 #include "xfa/fxfa/parser/cxfa_measurement.h"
24 #include "xfa/fxfa/parser/cxfa_node.h"
25 #include "xfa/fxfa/parser/cxfa_rectangle.h"
26 
27 namespace {
28 
Style3D(const std::vector<CXFA_Stroke * > & strokes)29 std::pair<XFA_AttributeValue, CXFA_Stroke*> Style3D(
30     const std::vector<CXFA_Stroke*>& strokes) {
31   if (strokes.empty())
32     return {XFA_AttributeValue::Unknown, nullptr};
33 
34   CXFA_Stroke* stroke = strokes[0];
35   for (size_t i = 1; i < strokes.size(); i++) {
36     CXFA_Stroke* find = strokes[i];
37     if (!find)
38       continue;
39     if (!stroke)
40       stroke = find;
41     else if (stroke->GetStrokeType() != find->GetStrokeType())
42       stroke = find;
43     break;
44   }
45 
46   XFA_AttributeValue iType = stroke->GetStrokeType();
47   if (iType == XFA_AttributeValue::Lowered ||
48       iType == XFA_AttributeValue::Raised ||
49       iType == XFA_AttributeValue::Etched ||
50       iType == XFA_AttributeValue::Embossed) {
51     return {iType, stroke};
52   }
53   return {XFA_AttributeValue::Unknown, stroke};
54 }
55 
ToRectangle(CXFA_Box * box)56 CXFA_Rectangle* ToRectangle(CXFA_Box* box) {
57   return static_cast<CXFA_Rectangle*>(box);
58 }
59 
60 }  // namespace
61 
CXFA_Box(CXFA_Document * pDoc,XFA_PacketType ePacket,uint32_t validPackets,XFA_ObjectType oType,XFA_Element eType,pdfium::span<const PropertyData> properties,pdfium::span<const AttributeData> attributes,CJX_Object * js_node)62 CXFA_Box::CXFA_Box(CXFA_Document* pDoc,
63                    XFA_PacketType ePacket,
64                    uint32_t validPackets,
65                    XFA_ObjectType oType,
66                    XFA_Element eType,
67                    pdfium::span<const PropertyData> properties,
68                    pdfium::span<const AttributeData> attributes,
69                    CJX_Object* js_node)
70     : CXFA_Node(pDoc,
71                 ePacket,
72                 validPackets,
73                 oType,
74                 eType,
75                 properties,
76                 attributes,
77                 js_node) {}
78 
79 CXFA_Box::~CXFA_Box() = default;
80 
GetHand()81 XFA_AttributeValue CXFA_Box::GetHand() {
82   return JSObject()->GetEnum(XFA_Attribute::Hand);
83 }
84 
GetPresence()85 XFA_AttributeValue CXFA_Box::GetPresence() {
86   return JSObject()
87       ->TryEnum(XFA_Attribute::Presence, true)
88       .value_or(XFA_AttributeValue::Visible);
89 }
90 
CountEdges()91 int32_t CXFA_Box::CountEdges() {
92   return CountChildren(XFA_Element::Edge, false);
93 }
94 
GetEdgeIfExists(int32_t nIndex)95 CXFA_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 
GetStrokes()102 std::vector<CXFA_Stroke*> CXFA_Box::GetStrokes() {
103   return GetStrokesInternal(false);
104 }
105 
IsCircular()106 bool CXFA_Box::IsCircular() {
107   return JSObject()->GetBoolean(XFA_Attribute::Circular);
108 }
109 
GetStartAngle()110 Optional<int32_t> CXFA_Box::GetStartAngle() {
111   return JSObject()->TryInteger(XFA_Attribute::StartAngle, false);
112 }
113 
GetSweepAngle()114 Optional<int32_t> CXFA_Box::GetSweepAngle() {
115   return JSObject()->TryInteger(XFA_Attribute::SweepAngle, false);
116 }
117 
GetOrCreateFillIfPossible()118 CXFA_Fill* CXFA_Box::GetOrCreateFillIfPossible() {
119   return JSObject()->GetOrCreateProperty<CXFA_Fill>(0, XFA_Element::Fill);
120 }
121 
Get3DStyle()122 std::tuple<XFA_AttributeValue, bool, float> CXFA_Box::Get3DStyle() {
123   if (GetElementType() == XFA_Element::Arc)
124     return {XFA_AttributeValue::Unknown, false, 0.0f};
125 
126   std::vector<CXFA_Stroke*> strokes = GetStrokesInternal(true);
127   CXFA_Stroke* stroke;
128   XFA_AttributeValue iType;
129 
130   std::tie(iType, stroke) = Style3D(strokes);
131   if (iType == XFA_AttributeValue::Unknown)
132     return {XFA_AttributeValue::Unknown, false, 0.0f};
133 
134   return {iType, stroke->IsVisible(), stroke->GetThickness()};
135 }
136 
GetStrokesInternal(bool bNull)137 std::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 
Draw(CFGAS_GEGraphics * pGS,const CFX_RectF & rtWidget,const CFX_Matrix & matrix,bool forceRound)183 void CXFA_Box::Draw(CFGAS_GEGraphics* pGS,
184                     const CFX_RectF& rtWidget,
185                     const CFX_Matrix& matrix,
186                     bool forceRound) {
187   if (GetPresence() != XFA_AttributeValue::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 
DrawFill(const std::vector<CXFA_Stroke * > & strokes,CFGAS_GEGraphics * pGS,CFX_RectF rtWidget,const CFX_Matrix & matrix,bool forceRound)210 void CXFA_Box::DrawFill(const std::vector<CXFA_Stroke*>& strokes,
211                         CFGAS_GEGraphics* 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   CFGAS_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_AttributeValue iHand = GetHand();
228     if (iHand == XFA_AttributeValue::Left)
229       rtWidget.Inflate(fHalf, fHalf);
230     else if (iHand == XFA_AttributeValue::Right)
231       rtWidget.Deflate(fHalf, fHalf);
232 
233     GetPathArcOrRounded(rtWidget, forceRound, &fillPath);
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 
GetPathArcOrRounded(CFX_RectF rtDraw,bool forceRound,CFGAS_GEPath * fillPath)245 void CXFA_Box::GetPathArcOrRounded(CFX_RectF rtDraw,
246                                    bool forceRound,
247                                    CFGAS_GEPath* fillPath) {
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 
StrokeArcOrRounded(CFGAS_GEGraphics * pGS,CFX_RectF rtWidget,const CFX_Matrix & matrix,bool forceRound)271 void CXFA_Box::StrokeArcOrRounded(CFGAS_GEGraphics* 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_AttributeValue i3DType;
282   std::tie(i3DType, bVisible, fThickness) = Get3DStyle();
283   bool lowered3d = false;
284   if (i3DType != XFA_AttributeValue::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_AttributeValue iHand = GetHand();
295   if (iHand == XFA_AttributeValue::Left) {
296     rtWidget.Inflate(fHalf, fHalf);
297   } else if (iHand == XFA_AttributeValue::Right) {
298     rtWidget.Deflate(fHalf, fHalf);
299   }
300   if (!forceRound || !lowered3d) {
301     if (fHalf < 0.001f)
302       return;
303 
304     CFGAS_GEPath arcPath;
305     GetPathArcOrRounded(rtWidget, forceRound, &arcPath);
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   CFGAS_GEPath arcPath;
328   arcPath.AddArc(rtWidget.TopLeft(), rtWidget.Size(), 3.0f * FX_PI / 4.0f,
329                  FX_PI);
330 
331   pGS->SetStrokeColor(CFGAS_GEColor(0xFF808080));
332   pGS->StrokePath(&arcPath, &matrix);
333   arcPath.Clear();
334   arcPath.AddArc(rtWidget.TopLeft(), rtWidget.Size(), -1.0f * FX_PI / 4.0f,
335                  FX_PI);
336 
337   pGS->SetStrokeColor(CFGAS_GEColor(0xFFFFFFFF));
338   pGS->StrokePath(&arcPath, &matrix);
339   rtWidget.Deflate(fHalf, fHalf);
340   arcPath.Clear();
341   arcPath.AddArc(rtWidget.TopLeft(), rtWidget.Size(), 3.0f * FX_PI / 4.0f,
342                  FX_PI);
343 
344   pGS->SetStrokeColor(CFGAS_GEColor(0xFF404040));
345   pGS->StrokePath(&arcPath, &matrix);
346   arcPath.Clear();
347   arcPath.AddArc(rtWidget.TopLeft(), rtWidget.Size(), -1.0f * FX_PI / 4.0f,
348                  FX_PI);
349 
350   pGS->SetStrokeColor(CFGAS_GEColor(0xFFC0C0C0));
351   pGS->StrokePath(&arcPath, &matrix);
352   pGS->RestoreGraphState();
353 }
354