blob: 60e40b46610cd408aae0597508d80e73a57338bc [file] [log] [blame] [edit]
// Copyright 2016 The PDFium Authors
// 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 <math.h>
#include <algorithm>
#include <utility>
#include "core/fxcrt/notreached.h"
#include "core/fxcrt/numerics/safe_conversions.h"
#include "fxjs/xfa/cjx_object.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,
Mask<XFA_XDPPACKET> 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);
}
size_t CXFA_Box::CountEdges() {
return CountChildren(XFA_Element::Edge, false);
}
CXFA_Edge* CXFA_Box::GetEdgeIfExists(size_t nIndex) {
if (nIndex == 0) {
return JSObject()->GetOrCreateProperty<CXFA_Edge>(
pdfium::checked_cast<int32_t>(nIndex), XFA_Element::Edge);
}
return JSObject()->GetProperty<CXFA_Edge>(
pdfium::checked_cast<int32_t>(nIndex), XFA_Element::Edge);
}
std::vector<CXFA_Stroke*> CXFA_Box::GetStrokes() {
return GetStrokesInternal(false);
}
bool CXFA_Box::IsCircular() {
return JSObject()->GetBoolean(XFA_Attribute::Circular);
}
std::optional<int32_t> CXFA_Box::GetStartAngle() {
return JSObject()->TryInteger(XFA_Attribute::StartAngle, false);
}
std::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_NORETURN();
}
}
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;
CFGAS_GEPath fillPath;
XFA_Element type = GetElementType();
CFGAS_GEGraphics::StateRestorer restorer(pGS);
if (type == XFA_Element::Arc || forceRound) {
CXFA_Edge* edge = GetEdgeIfExists(0);
float fThickness = 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_NORETURN();
}
fillPath.Close();
fill->Draw(pGS, fillPath, rtWidget, matrix);
}
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;
std::optional<int32_t> startAngle = GetStartAngle();
std::optional<int32_t> sweepAngle = GetSweepAngle();
if (!startAngle.has_value() && !sweepAngle.has_value()) {
fillPath->AddEllipse(rtDraw);
return;
}
fillPath->AddArc(rtDraw.TopLeft(), rtDraw.Size(),
-startAngle.value_or(0) * FXSYS_PI / 180.0f,
-sweepAngle.value_or(360) * FXSYS_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;
}
CFGAS_GEGraphics::StateRestorer restorer(pGS);
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 * FXSYS_PI / 4.0f,
FXSYS_PI);
pGS->SetStrokeColor(CFGAS_GEColor(0xFF808080));
pGS->StrokePath(arcPath, matrix);
arcPath.Clear();
arcPath.AddArc(rtWidget.TopLeft(), rtWidget.Size(), -1.0f * FXSYS_PI / 4.0f,
FXSYS_PI);
pGS->SetStrokeColor(CFGAS_GEColor(0xFFFFFFFF));
pGS->StrokePath(arcPath, matrix);
rtWidget.Deflate(fHalf, fHalf);
arcPath.Clear();
arcPath.AddArc(rtWidget.TopLeft(), rtWidget.Size(), 3.0f * FXSYS_PI / 4.0f,
FXSYS_PI);
pGS->SetStrokeColor(CFGAS_GEColor(0xFF404040));
pGS->StrokePath(arcPath, matrix);
arcPath.Clear();
arcPath.AddArc(rtWidget.TopLeft(), rtWidget.Size(), -1.0f * FXSYS_PI / 4.0f,
FXSYS_PI);
pGS->SetStrokeColor(CFGAS_GEColor(0xFFC0C0C0));
pGS->StrokePath(arcPath, matrix);
}