| // Copyright 2016 PDFium Authors. All rights reserved. | |
| // Use of this source code is governed by a BSD-style license that can be | |
| // found in the LICENSE file. | |
| // Original code copyright 2014 Foxit Software Inc. http://www.foxitsoftware.com | |
| #include "xfa/fxfa/parser/cxfa_box.h" | |
| #include <algorithm> | |
| #include <cmath> | |
| #include <utility> | |
| #include "fxjs/xfa/cjx_object.h" | |
| #include "third_party/base/notreached.h" | |
| #include "xfa/fgas/graphics/cfgas_gegraphics.h" | |
| #include "xfa/fgas/graphics/cfgas_gepath.h" | |
| #include "xfa/fgas/graphics/cfgas_gepattern.h" | |
| #include "xfa/fgas/graphics/cfgas_geshading.h" | |
| #include "xfa/fxfa/parser/cxfa_corner.h" | |
| #include "xfa/fxfa/parser/cxfa_edge.h" | |
| #include "xfa/fxfa/parser/cxfa_fill.h" | |
| #include "xfa/fxfa/parser/cxfa_margin.h" | |
| #include "xfa/fxfa/parser/cxfa_measurement.h" | |
| #include "xfa/fxfa/parser/cxfa_node.h" | |
| #include "xfa/fxfa/parser/cxfa_rectangle.h" | |
| namespace { | |
| std::pair<XFA_AttributeValue, CXFA_Stroke*> Style3D( | |
| const std::vector<CXFA_Stroke*>& strokes) { | |
| if (strokes.empty()) | |
| return {XFA_AttributeValue::Unknown, nullptr}; | |
| CXFA_Stroke* stroke = strokes[0]; | |
| for (size_t i = 1; i < strokes.size(); i++) { | |
| CXFA_Stroke* find = strokes[i]; | |
| if (!find) | |
| continue; | |
| if (!stroke) | |
| stroke = find; | |
| else if (stroke->GetStrokeType() != find->GetStrokeType()) | |
| stroke = find; | |
| break; | |
| } | |
| XFA_AttributeValue iType = stroke->GetStrokeType(); | |
| if (iType == XFA_AttributeValue::Lowered || | |
| iType == XFA_AttributeValue::Raised || | |
| iType == XFA_AttributeValue::Etched || | |
| iType == XFA_AttributeValue::Embossed) { | |
| return {iType, stroke}; | |
| } | |
| return {XFA_AttributeValue::Unknown, stroke}; | |
| } | |
| CXFA_Rectangle* ToRectangle(CXFA_Box* box) { | |
| return static_cast<CXFA_Rectangle*>(box); | |
| } | |
| } // namespace | |
| CXFA_Box::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) | |
| : CXFA_Node(pDoc, | |
| ePacket, | |
| validPackets, | |
| oType, | |
| eType, | |
| properties, | |
| attributes, | |
| js_node) {} | |
| CXFA_Box::~CXFA_Box() = default; | |
| XFA_AttributeValue CXFA_Box::GetHand() { | |
| return JSObject()->GetEnum(XFA_Attribute::Hand); | |
| } | |
| XFA_AttributeValue CXFA_Box::GetPresence() { | |
| return JSObject() | |
| ->TryEnum(XFA_Attribute::Presence, true) | |
| .value_or(XFA_AttributeValue::Visible); | |
| } | |
| int32_t CXFA_Box::CountEdges() { | |
| return CountChildren(XFA_Element::Edge, false); | |
| } | |
| CXFA_Edge* CXFA_Box::GetEdgeIfExists(int32_t nIndex) { | |
| if (nIndex == 0) | |
| return JSObject()->GetOrCreateProperty<CXFA_Edge>(nIndex, | |
| XFA_Element::Edge); | |
| return JSObject()->GetProperty<CXFA_Edge>(nIndex, XFA_Element::Edge); | |
| } | |
| std::vector<CXFA_Stroke*> CXFA_Box::GetStrokes() { | |
| return GetStrokesInternal(false); | |
| } | |
| bool CXFA_Box::IsCircular() { | |
| return JSObject()->GetBoolean(XFA_Attribute::Circular); | |
| } | |
| Optional<int32_t> CXFA_Box::GetStartAngle() { | |
| return JSObject()->TryInteger(XFA_Attribute::StartAngle, false); | |
| } | |
| Optional<int32_t> CXFA_Box::GetSweepAngle() { | |
| return JSObject()->TryInteger(XFA_Attribute::SweepAngle, false); | |
| } | |
| CXFA_Fill* CXFA_Box::GetOrCreateFillIfPossible() { | |
| return JSObject()->GetOrCreateProperty<CXFA_Fill>(0, XFA_Element::Fill); | |
| } | |
| std::tuple<XFA_AttributeValue, bool, float> CXFA_Box::Get3DStyle() { | |
| if (GetElementType() == XFA_Element::Arc) | |
| return {XFA_AttributeValue::Unknown, false, 0.0f}; | |
| std::vector<CXFA_Stroke*> strokes = GetStrokesInternal(true); | |
| CXFA_Stroke* stroke; | |
| XFA_AttributeValue iType; | |
| std::tie(iType, stroke) = Style3D(strokes); | |
| if (iType == XFA_AttributeValue::Unknown) | |
| return {XFA_AttributeValue::Unknown, false, 0.0f}; | |
| return {iType, stroke->IsVisible(), stroke->GetThickness()}; | |
| } | |
| std::vector<CXFA_Stroke*> CXFA_Box::GetStrokesInternal(bool bNull) { | |
| std::vector<CXFA_Stroke*> strokes; | |
| strokes.resize(8); | |
| for (int32_t i = 0, j = 0; i < 4; i++) { | |
| CXFA_Corner* corner; | |
| if (i == 0) { | |
| corner = | |
| JSObject()->GetOrCreateProperty<CXFA_Corner>(i, XFA_Element::Corner); | |
| } else { | |
| corner = JSObject()->GetProperty<CXFA_Corner>(i, XFA_Element::Corner); | |
| } | |
| // TODO(dsinclair): If i == 0 and GetOrCreateProperty failed, we can end up | |
| // with a null corner in the first position. | |
| if (corner || i == 0) { | |
| strokes[j] = corner; | |
| } else if (!bNull) { | |
| if (i == 1 || i == 2) | |
| strokes[j] = strokes[0]; | |
| else | |
| strokes[j] = strokes[2]; | |
| } | |
| j++; | |
| CXFA_Edge* edge; | |
| if (i == 0) | |
| edge = JSObject()->GetOrCreateProperty<CXFA_Edge>(i, XFA_Element::Edge); | |
| else | |
| edge = JSObject()->GetProperty<CXFA_Edge>(i, XFA_Element::Edge); | |
| // TODO(dsinclair): If i == 0 and GetOrCreateProperty failed, we can end up | |
| // with a null edge in the first position. | |
| if (edge || i == 0) { | |
| strokes[j] = edge; | |
| } else if (!bNull) { | |
| if (i == 1 || i == 2) | |
| strokes[j] = strokes[1]; | |
| else | |
| strokes[j] = strokes[3]; | |
| } | |
| j++; | |
| } | |
| return strokes; | |
| } | |
| void CXFA_Box::Draw(CFGAS_GEGraphics* pGS, | |
| const CFX_RectF& rtWidget, | |
| const CFX_Matrix& matrix, | |
| bool forceRound) { | |
| if (GetPresence() != XFA_AttributeValue::Visible) | |
| return; | |
| XFA_Element eType = GetElementType(); | |
| if (eType != XFA_Element::Arc && eType != XFA_Element::Border && | |
| eType != XFA_Element::Rectangle) { | |
| return; | |
| } | |
| std::vector<CXFA_Stroke*> strokes; | |
| if (!forceRound && eType != XFA_Element::Arc) | |
| strokes = GetStrokes(); | |
| DrawFill(strokes, pGS, rtWidget, matrix, forceRound); | |
| XFA_Element type = GetElementType(); | |
| if (type == XFA_Element::Arc || forceRound) { | |
| StrokeArcOrRounded(pGS, rtWidget, matrix, forceRound); | |
| } else if (type == XFA_Element::Rectangle || type == XFA_Element::Border) { | |
| ToRectangle(this)->Draw(strokes, pGS, rtWidget, matrix); | |
| } else { | |
| NOTREACHED(); | |
| } | |
| } | |
| void CXFA_Box::DrawFill(const std::vector<CXFA_Stroke*>& strokes, | |
| CFGAS_GEGraphics* pGS, | |
| CFX_RectF rtWidget, | |
| const CFX_Matrix& matrix, | |
| bool forceRound) { | |
| CXFA_Fill* fill = JSObject()->GetProperty<CXFA_Fill>(0, XFA_Element::Fill); | |
| if (!fill || !fill->IsVisible()) | |
| return; | |
| pGS->SaveGraphState(); | |
| CFGAS_GEPath fillPath; | |
| XFA_Element type = GetElementType(); | |
| if (type == XFA_Element::Arc || forceRound) { | |
| CXFA_Edge* edge = GetEdgeIfExists(0); | |
| float fThickness = std::fmax(0.0, edge ? edge->GetThickness() : 0); | |
| float fHalf = fThickness / 2; | |
| XFA_AttributeValue iHand = GetHand(); | |
| if (iHand == XFA_AttributeValue::Left) | |
| rtWidget.Inflate(fHalf, fHalf); | |
| else if (iHand == XFA_AttributeValue::Right) | |
| rtWidget.Deflate(fHalf, fHalf); | |
| GetPathArcOrRounded(rtWidget, forceRound, &fillPath); | |
| } else if (type == XFA_Element::Rectangle || type == XFA_Element::Border) { | |
| ToRectangle(this)->GetFillPath(strokes, rtWidget, &fillPath); | |
| } else { | |
| NOTREACHED(); | |
| } | |
| fillPath.Close(); | |
| fill->Draw(pGS, fillPath, rtWidget, matrix); | |
| pGS->RestoreGraphState(); | |
| } | |
| void CXFA_Box::GetPathArcOrRounded(CFX_RectF rtDraw, | |
| bool forceRound, | |
| CFGAS_GEPath* fillPath) { | |
| float a = rtDraw.width / 2.0f; | |
| float b = rtDraw.height / 2.0f; | |
| if (IsCircular() || forceRound) | |
| a = b = std::min(a, b); | |
| CFX_PointF center = rtDraw.Center(); | |
| rtDraw.left = center.x - a; | |
| rtDraw.top = center.y - b; | |
| rtDraw.width = a + a; | |
| rtDraw.height = b + b; | |
| Optional<int32_t> startAngle = GetStartAngle(); | |
| Optional<int32_t> sweepAngle = GetSweepAngle(); | |
| if (!startAngle && !sweepAngle) { | |
| fillPath->AddEllipse(rtDraw); | |
| return; | |
| } | |
| fillPath->AddArc(rtDraw.TopLeft(), rtDraw.Size(), | |
| -startAngle.value_or(0) * FX_PI / 180.0f, | |
| -sweepAngle.value_or(360) * FX_PI / 180.0f); | |
| } | |
| void CXFA_Box::StrokeArcOrRounded(CFGAS_GEGraphics* pGS, | |
| CFX_RectF rtWidget, | |
| const CFX_Matrix& matrix, | |
| bool forceRound) { | |
| CXFA_Edge* edge = GetEdgeIfExists(0); | |
| if (!edge || !edge->IsVisible()) | |
| return; | |
| bool bVisible; | |
| float fThickness; | |
| XFA_AttributeValue i3DType; | |
| std::tie(i3DType, bVisible, fThickness) = Get3DStyle(); | |
| bool lowered3d = false; | |
| if (i3DType != XFA_AttributeValue::Unknown) { | |
| if (bVisible && fThickness >= 0.001f) | |
| lowered3d = true; | |
| } | |
| float fHalf = edge->GetThickness() / 2; | |
| if (fHalf < 0) { | |
| fHalf = 0; | |
| } | |
| XFA_AttributeValue iHand = GetHand(); | |
| if (iHand == XFA_AttributeValue::Left) { | |
| rtWidget.Inflate(fHalf, fHalf); | |
| } else if (iHand == XFA_AttributeValue::Right) { | |
| rtWidget.Deflate(fHalf, fHalf); | |
| } | |
| if (!forceRound || !lowered3d) { | |
| if (fHalf < 0.001f) | |
| return; | |
| CFGAS_GEPath arcPath; | |
| GetPathArcOrRounded(rtWidget, forceRound, &arcPath); | |
| if (edge) | |
| edge->Stroke(pGS, arcPath, matrix); | |
| return; | |
| } | |
| pGS->SaveGraphState(); | |
| pGS->SetLineWidth(fHalf); | |
| float a = rtWidget.width / 2.0f; | |
| float b = rtWidget.height / 2.0f; | |
| if (forceRound) { | |
| a = std::min(a, b); | |
| b = a; | |
| } | |
| CFX_PointF center = rtWidget.Center(); | |
| rtWidget.left = center.x - a; | |
| rtWidget.top = center.y - b; | |
| rtWidget.width = a + a; | |
| rtWidget.height = b + b; | |
| CFGAS_GEPath arcPath; | |
| arcPath.AddArc(rtWidget.TopLeft(), rtWidget.Size(), 3.0f * FX_PI / 4.0f, | |
| FX_PI); | |
| pGS->SetStrokeColor(CFGAS_GEColor(0xFF808080)); | |
| pGS->StrokePath(arcPath, matrix); | |
| arcPath.Clear(); | |
| arcPath.AddArc(rtWidget.TopLeft(), rtWidget.Size(), -1.0f * FX_PI / 4.0f, | |
| FX_PI); | |
| pGS->SetStrokeColor(CFGAS_GEColor(0xFFFFFFFF)); | |
| pGS->StrokePath(arcPath, matrix); | |
| rtWidget.Deflate(fHalf, fHalf); | |
| arcPath.Clear(); | |
| arcPath.AddArc(rtWidget.TopLeft(), rtWidget.Size(), 3.0f * FX_PI / 4.0f, | |
| FX_PI); | |
| pGS->SetStrokeColor(CFGAS_GEColor(0xFF404040)); | |
| pGS->StrokePath(arcPath, matrix); | |
| arcPath.Clear(); | |
| arcPath.AddArc(rtWidget.TopLeft(), rtWidget.Size(), -1.0f * FX_PI / 4.0f, | |
| FX_PI); | |
| pGS->SetStrokeColor(CFGAS_GEColor(0xFFC0C0C0)); | |
| pGS->StrokePath(arcPath, matrix); | |
| pGS->RestoreGraphState(); | |
| } |