text: // Copyright 2017 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_rectangle.h"
  
  #include <cmath>
  #include <utility>
  
  #include "fxjs/xfa/cjx_node.h"
  #include "third_party/base/check.h"
  #include "third_party/base/notreached.h"
  #include "xfa/fxfa/parser/cxfa_corner.h"
  #include "xfa/fxfa/parser/cxfa_document.h"
  #include "xfa/fxfa/parser/cxfa_stroke.h"
  
  namespace {
  
  const CXFA_Node::PropertyData kRectanglePropertyData[] = {
      {XFA_Element::Edge, 4, 0},
      {XFA_Element::Corner, 4, 0},
      {XFA_Element::Fill, 1, 0},
  };
  
  const CXFA_Node::AttributeData kRectangleAttributeData[] = {
      {XFA_Attribute::Id, XFA_AttributeType::CData, nullptr},
      {XFA_Attribute::Use, XFA_AttributeType::CData, nullptr},
      {XFA_Attribute::Usehref, XFA_AttributeType::CData, nullptr},
      {XFA_Attribute::Hand, XFA_AttributeType::Enum,
       (void*)XFA_AttributeValue::Even},
  };
  
  }  // namespace
  
  CXFA_Rectangle::CXFA_Rectangle(CXFA_Document* doc, XFA_PacketType packet)
      : CXFA_Box(doc,
                 packet,
                 (XFA_XDPPACKET_Template | XFA_XDPPACKET_Form),
                 XFA_ObjectType::Node,
                 XFA_Element::Rectangle,
                 kRectanglePropertyData,
                 kRectangleAttributeData,
                 cppgc::MakeGarbageCollected<CJX_Node>(
                     doc->GetHeap()->GetAllocationHandle(),
                     this)) {}
  
  CXFA_Rectangle::CXFA_Rectangle(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_Box(pDoc,
                 ePacket,
                 validPackets,
                 oType,
                 eType,
                 properties,
                 attributes,
                 js_node) {}
  
  CXFA_Rectangle::~CXFA_Rectangle() = default;
  
  void CXFA_Rectangle::GetFillPath(const std::vector<CXFA_Stroke*>& strokes,
                                   const CFX_RectF& rtWidget,
                                   CFGAS_GEPath* fillPath) {
    bool bSameStyles = true;
    CXFA_Stroke* stroke1 = strokes[0];
    for (int32_t i = 1; i < 8; i++) {
      CXFA_Stroke* stroke2 = strokes[i];
      if (!stroke1->SameStyles(stroke2, 0)) {
        bSameStyles = false;
        break;
      }
      stroke1 = stroke2;
    }
  
    if (bSameStyles) {
      stroke1 = strokes[0];
      for (int32_t i = 2; i < 8; i += 2) {
        CXFA_Stroke* stroke2 = strokes[i];
        if (!stroke1->SameStyles(stroke2, XFA_STROKE_SAMESTYLE_NoPresence |
                                              XFA_STROKE_SAMESTYLE_Corner)) {
          bSameStyles = false;
          break;
        }
        stroke1 = stroke2;
      }
      if (bSameStyles) {
        stroke1 = strokes[0];
        if (stroke1->IsInverted())
          bSameStyles = false;
        if (stroke1->GetJoinType() != XFA_AttributeValue::Square)
          bSameStyles = false;
      }
    }
    if (bSameStyles) {
      fillPath->AddRectangle(rtWidget.left, rtWidget.top, rtWidget.width,
                             rtWidget.height);
      return;
    }
  
    for (int32_t i = 0; i < 8; i += 2) {
      float sx = 0.0f;
      float sy = 0.0f;
      float vx = 1.0f;
      float vy = 1.0f;
      float nx = 1.0f;
      float ny = 1.0f;
      CFX_PointF cp1;
      CFX_PointF cp2;
      CXFA_Stroke* corner1 = strokes[i];
      CXFA_Stroke* corner2 = strokes[(i + 2) % 8];
      float fRadius1 = corner1->GetRadius();
      float fRadius2 = corner2->GetRadius();
      bool bInverted = corner1->IsInverted();
      bool bRound = corner1->GetJoinType() == XFA_AttributeValue::Round;
      if (bRound) {
        sy = FX_PI / 2;
      }
      switch (i) {
        case 0:
          cp1 = rtWidget.TopLeft();
          cp2 = rtWidget.TopRight();
          vx = 1;
          vy = 1;
          nx = -1;
          ny = 0;
          if (bRound) {
            sx = bInverted ? FX_PI / 2 : FX_PI;
          } else {
            sx = 1;
            sy = 0;
          }
          break;
        case 2:
          cp1 = rtWidget.TopRight();
          cp2 = rtWidget.BottomRight();
          vx = -1;
          vy = 1;
          nx = 0;
          ny = -1;
          if (bRound) {
            sx = bInverted ? FX_PI : FX_PI * 3 / 2;
          } else {
            sx = 0;
            sy = 1;
          }
          break;
        case 4:
          cp1 = rtWidget.BottomRight();
          cp2 = rtWidget.BottomLeft();
          vx = -1;
          vy = -1;
          nx = 1;
          ny = 0;
          if (bRound) {
            sx = bInverted ? FX_PI * 3 / 2 : 0;
          } else {
            sx = -1;
            sy = 0;
          }
          break;
        case 6:
          cp1 = rtWidget.BottomLeft();
          cp2 = rtWidget.TopLeft();
          vx = 1;
          vy = -1;
          nx = 0;
          ny = 1;
          if (bRound) {
            sx = bInverted ? 0 : FX_PI / 2;
          } else {
            sx = 0;
            sy = -1;
          }
          break;
      }
      if (i == 0)
        fillPath->MoveTo(CFX_PointF(cp1.x, cp1.y + fRadius1));
  
      if (bRound) {
        if (fRadius1 < 0)
          sx -= FX_PI;
        if (bInverted)
          sy *= -1;
  
        CFX_RectF rtRadius(cp1.x, cp1.y, fRadius1 * 2 * vx, fRadius1 * 2 * vy);
        rtRadius.Normalize();
        if (bInverted)
          rtRadius.Offset(-fRadius1 * vx, -fRadius1 * vy);
  
        fillPath->ArcTo(rtRadius.TopLeft(), rtRadius.Size(), sx, sy);
      } else {
        CFX_PointF cp;
        if (bInverted) {
          cp.x = cp1.x + fRadius1 * vx;
          cp.y = cp1.y + fRadius1 * vy;
        } else {
          cp = cp1;
        }
        fillPath->LineTo(cp);
        fillPath->LineTo(
            CFX_PointF(cp1.x + fRadius1 * sx, cp1.y + fRadius1 * sy));
      }
      fillPath->LineTo(CFX_PointF(cp2.x + fRadius2 * nx, cp2.y + fRadius2 * ny));
    }
  }
  
  void CXFA_Rectangle::Draw(const std::vector<CXFA_Stroke*>& strokes,
                            CFGAS_GEGraphics* pGS,
                            CFX_RectF rtWidget,
                            const CFX_Matrix& matrix) {
    bool bVisible = false;
    for (int32_t j = 0; j < 4; j++) {
      if (strokes[j * 2 + 1]->IsVisible()) {
        bVisible = true;
        break;
      }
    }
    if (!bVisible)
      return;
  
    for (int32_t i = 1; i < 8; i += 2) {
      float fThickness = std::fmax(0.0, strokes[i]->GetThickness());
      float fHalf = fThickness / 2;
      XFA_AttributeValue iHand = GetHand();
      switch (i) {
        case 1:
          if (iHand == XFA_AttributeValue::Left) {
            rtWidget.top -= fHalf;
            rtWidget.height += fHalf;
          } else if (iHand == XFA_AttributeValue::Right) {
            rtWidget.top += fHalf;
            rtWidget.height -= fHalf;
          }
          break;
        case 3:
          if (iHand == XFA_AttributeValue::Left) {
            rtWidget.width += fHalf;
          } else if (iHand == XFA_AttributeValue::Right) {
            rtWidget.width -= fHalf;
          }
          break;
        case 5:
          if (iHand == XFA_AttributeValue::Left) {
            rtWidget.height += fHalf;
          } else if (iHand == XFA_AttributeValue::Right) {
            rtWidget.height -= fHalf;
          }
          break;
        case 7:
          if (iHand == XFA_AttributeValue::Left) {
            rtWidget.left -= fHalf;
            rtWidget.width += fHalf;
          } else if (iHand == XFA_AttributeValue::Right) {
            rtWidget.left += fHalf;
            rtWidget.width -= fHalf;
          }
          break;
      }
    }
    Stroke(strokes, pGS, rtWidget, matrix);
  }
  
  void CXFA_Rectangle::Stroke(const std::vector<CXFA_Stroke*>& strokes,
                              CFGAS_GEGraphics* pGS,
                              CFX_RectF rtWidget,
                              const CFX_Matrix& matrix) {
    bool bVisible;
    float fThickness;
    XFA_AttributeValue i3DType;
    std::tie(i3DType, bVisible, fThickness) = Get3DStyle();
    if (i3DType != XFA_AttributeValue::Unknown) {
      if (!bVisible || fThickness < 0.001f)
        return;
  
      switch (i3DType) {
        case XFA_AttributeValue::Lowered:
          StrokeLowered(pGS, rtWidget, fThickness, matrix);
          break;
        case XFA_AttributeValue::Raised:
          StrokeRaised(pGS, rtWidget, fThickness, matrix);
          break;
        case XFA_AttributeValue::Etched:
          StrokeEtched(pGS, rtWidget, fThickness, matrix);
          break;
        case XFA_AttributeValue::Embossed:
          StrokeEmbossed(pGS, rtWidget, fThickness, matrix);
          break;
        default:
          NOTREACHED();
          break;
      }
      return;
    }
  
    bool bClose = false;
    bool bSameStyles = true;
    CXFA_Stroke* stroke1 = strokes[0];
    for (int32_t i = 1; i < 8; i++) {
      CXFA_Stroke* stroke2 = strokes[i];
      if (!stroke1->SameStyles(stroke2, 0)) {
        bSameStyles = false;
        break;
      }
      stroke1 = stroke2;
    }
    if (bSameStyles) {
      stroke1 = strokes[0];
      bClose = true;
      for (int32_t i = 2; i < 8; i += 2) {
        CXFA_Stroke* stroke2 = strokes[i];
        if (!stroke1->SameStyles(stroke2, XFA_STROKE_SAMESTYLE_NoPresence |
                                              XFA_STROKE_SAMESTYLE_Corner)) {
          bSameStyles = false;
          break;
        }
        stroke1 = stroke2;
      }
      if (bSameStyles) {
        stroke1 = strokes[0];
        if (stroke1->IsInverted())
          bSameStyles = false;
        if (stroke1->GetJoinType() != XFA_AttributeValue::Square)
          bSameStyles = false;
      }
    }
  
    bool bStart = true;
    CFGAS_GEPath path;
    for (int32_t i = 0; i < 8; i++) {
      CXFA_Stroke* stroke = strokes[i];
      if ((i % 1) == 0 && stroke->GetRadius() < 0) {
        bool bEmpty = path.IsEmpty();
        if (!bEmpty) {
          if (stroke)
            stroke->Stroke(&path, pGS, matrix);
          path.Clear();
        }
        bStart = true;
        continue;
      }
      GetPath(strokes, rtWidget, path, i, bStart, !bSameStyles);
  
      bStart = !stroke->SameStyles(strokes[(i + 1) % 8], 0);
      if (bStart) {
        if (stroke)
          stroke->Stroke(&path, pGS, matrix);
        path.Clear();
      }
    }
    bool bEmpty = path.IsEmpty();
    if (!bEmpty) {
      if (bClose) {
        path.Close();
      }
      if (strokes[7])
        strokes[7]->Stroke(&path, pGS, matrix);
    }
  }
  
  void CXFA_Rectangle::StrokeRect(CFGAS_GEGraphics* pGraphic,
                                  const CFX_RectF& rt,
                                  float fLineWidth,
                                  const CFX_Matrix& matrix,
                                  FX_ARGB argbTopLeft,
                                  FX_ARGB argbBottomRight) {
    float fBottom = rt.bottom();
    float fRight = rt.right();
    CFGAS_GEPath pathLT;
    pathLT.MoveTo(CFX_PointF(rt.left, fBottom));
    pathLT.LineTo(CFX_PointF(rt.left, rt.top));
    pathLT.LineTo(CFX_PointF(fRight, rt.top));
    pathLT.LineTo(CFX_PointF(fRight - fLineWidth, rt.top + fLineWidth));
    pathLT.LineTo(CFX_PointF(rt.left + fLineWidth, rt.top + fLineWidth));
    pathLT.LineTo(CFX_PointF(rt.left + fLineWidth, fBottom - fLineWidth));
    pathLT.LineTo(CFX_PointF(rt.left, fBottom));
    pGraphic->SetFillColor(CFGAS_GEColor(argbTopLeft));
    pGraphic->FillPath(&pathLT, CFX_FillRenderOptions::FillType::kWinding,
                       matrix);
  
    CFGAS_GEPath pathRB;
    pathRB.MoveTo(CFX_PointF(fRight, rt.top));
    pathRB.LineTo(CFX_PointF(fRight, fBottom));
    pathRB.LineTo(CFX_PointF(rt.left, fBottom));
    pathRB.LineTo(CFX_PointF(rt.left + fLineWidth, fBottom - fLineWidth));
    pathRB.LineTo(CFX_PointF(fRight - fLineWidth, fBottom - fLineWidth));
    pathRB.LineTo(CFX_PointF(fRight - fLineWidth, rt.top + fLineWidth));
    pathRB.LineTo(CFX_PointF(fRight, rt.top));
    pGraphic->SetFillColor(CFGAS_GEColor(argbBottomRight));
    pGraphic->FillPath(&pathRB, CFX_FillRenderOptions::FillType::kWinding,
                       matrix);
  }
  
  void CXFA_Rectangle::StrokeLowered(CFGAS_GEGraphics* pGS,
                                     CFX_RectF rt,
                                     float fThickness,
                                     const CFX_Matrix& matrix) {
    float fHalfWidth = fThickness / 2.0f;
    CFX_RectF rtInner(rt);
    rtInner.Deflate(fHalfWidth, fHalfWidth);
  
    CFGAS_GEPath path;
    path.AddRectangle(rt.left, rt.top, rt.width, rt.height);
    path.AddRectangle(rtInner.left, rtInner.top, rtInner.width, rtInner.height);
    pGS->SetFillColor(CFGAS_GEColor(0xFF000000));
    pGS->FillPath(&path, CFX_FillRenderOptions::FillType::kEvenOdd, matrix);
    StrokeRect(pGS, rtInner, fHalfWidth, matrix, 0xFF808080, 0xFFC0C0C0);
  }
  
  void CXFA_Rectangle::StrokeRaised(CFGAS_GEGraphics* pGS,
                                    CFX_RectF rt,
                                    float fThickness,
                                    const CFX_Matrix& matrix) {
    float fHalfWidth = fThickness / 2.0f;
    CFX_RectF rtInner(rt);
    rtInner.Deflate(fHalfWidth, fHalfWidth);
  
    CFGAS_GEPath path;
    path.AddRectangle(rt.left, rt.top, rt.width, rt.height);
    path.AddRectangle(rtInner.left, rtInner.top, rtInner.width, rtInner.height);
    pGS->SetFillColor(CFGAS_GEColor(0xFF000000));
    pGS->FillPath(&path, CFX_FillRenderOptions::FillType::kEvenOdd, matrix);
    StrokeRect(pGS, rtInner, fHalfWidth, matrix, 0xFFFFFFFF, 0xFF808080);
  }
  
  void CXFA_Rectangle::StrokeEtched(CFGAS_GEGraphics* pGS,
                                    CFX_RectF rt,
                                    float fThickness,
                                    const CFX_Matrix& matrix) {
    float fHalfWidth = fThickness / 2.0f;
    StrokeRect(pGS, rt, fThickness, matrix, 0xFF808080, 0xFFFFFFFF);
  
    CFX_RectF rtInner(rt);
    rtInner.Deflate(fHalfWidth, fHalfWidth);
    StrokeRect(pGS, rtInner, fHalfWidth, matrix, 0xFFFFFFFF, 0xFF808080);
  }
  
  void CXFA_Rectangle::StrokeEmbossed(CFGAS_GEGraphics* pGS,
                                      CFX_RectF rt,
                                      float fThickness,
                                      const CFX_Matrix& matrix) {
    float fHalfWidth = fThickness / 2.0f;
    StrokeRect(pGS, rt, fThickness, matrix, 0xFF808080, 0xFF000000);
  
    CFX_RectF rtInner(rt);
    rtInner.Deflate(fHalfWidth, fHalfWidth);
    StrokeRect(pGS, rtInner, fHalfWidth, matrix, 0xFF000000, 0xFF808080);
  }
  
  void CXFA_Rectangle::GetPath(const std::vector<CXFA_Stroke*>& strokes,
                               CFX_RectF rtWidget,
                               CFGAS_GEPath& path,
                               int32_t nIndex,
                               bool bStart,
                               bool bCorner) {
    DCHECK(nIndex >= 0);
    DCHECK(nIndex < 8);
  
    int32_t n = (nIndex & 1) ? nIndex - 1 : nIndex;
    CXFA_Stroke* corner1 = strokes[n];
    CXFA_Stroke* corner2 = strokes[(n + 2) % 8];
    float fRadius1 = bCorner ? corner1->GetRadius() : 0.0f;
    float fRadius2 = bCorner ? corner2->GetRadius() : 0.0f;
    bool bInverted = corner1->IsInverted();
    float offsetY = 0.0f;
    float offsetX = 0.0f;
    bool bRound = corner1->GetJoinType() == XFA_AttributeValue::Round;
    float halfAfter = 0.0f;
    float halfBefore = 0.0f;
  
    CXFA_Stroke* stroke = strokes[nIndex];
    if (stroke->IsCorner()) {
      CXFA_Stroke* strokeBefore = strokes[(nIndex + 1 * 8 - 1) % 8];
      CXFA_Stroke* strokeAfter = strokes[nIndex + 1];
      if (stroke->IsInverted()) {
        if (!stroke->SameStyles(strokeBefore, 0))
          halfBefore = strokeBefore->GetThickness() / 2;
        if (!stroke->SameStyles(strokeAfter, 0))
          halfAfter = strokeAfter->GetThickness() / 2;
      }
    } else {
      CXFA_Stroke* strokeBefore = strokes[(nIndex + 8 - 2) % 8];
      CXFA_Stroke* strokeAfter = strokes[(nIndex + 2) % 8];
      if (!bRound && !bInverted) {
        halfBefore = strokeBefore->GetThickness() / 2;
        halfAfter = strokeAfter->GetThickness() / 2;
      }
    }
  
    float offsetEX = 0.0f;
    float offsetEY = 0.0f;
    float sx = 0.0f;
    float sy = 0.0f;
    float vx = 1.0f;
    float vy = 1.0f;
    float nx = 1.0f;
    float ny = 1.0f;
    CFX_PointF cpStart;
    CFX_PointF cp1;
    CFX_PointF cp2;
    if (bRound)
      sy = FX_PI / 2;
  
    switch (nIndex) {
      case 0:
      case 1:
        cp1 = rtWidget.TopLeft();
        cp2 = rtWidget.TopRight();
        if (nIndex == 0) {
          cpStart.x = cp1.x - halfBefore;
          cpStart.y = cp1.y + fRadius1;
          offsetY = -halfAfter;
        } else {
          cpStart.x = cp1.x + fRadius1 - halfBefore;
          cpStart.y = cp1.y;
          offsetEX = halfAfter;
        }
        vx = 1;
        vy = 1;
        nx = -1;
        ny = 0;
        if (bRound) {
          sx = bInverted ? FX_PI / 2 : FX_PI;
        } else {
          sx = 1;
          sy = 0;
        }
        break;
      case 2:
      case 3:
        cp1 = rtWidget.TopRight();
        cp2 = rtWidget.BottomRight();
        if (nIndex == 2) {
          cpStart.x = cp1.x - fRadius1;
          cpStart.y = cp1.y - halfBefore;
          offsetX = halfAfter;
        } else {
          cpStart.x = cp1.x;
          cpStart.y = cp1.y + fRadius1 - halfBefore;
          offsetEY = halfAfter;
        }
        vx = -1;
        vy = 1;
        nx = 0;
        ny = -1;
        if (bRound) {
          sx = bInverted ? FX_PI : FX_PI * 3 / 2;
        } else {
          sx = 0;
          sy = 1;
        }
        break;
      case 4:
      case 5:
        cp1 = rtWidget.BottomRight();
        cp2 = rtWidget.BottomLeft();
        if (nIndex == 4) {
          cpStart.x = cp1.x + halfBefore;
          cpStart.y = cp1.y - fRadius1;
          offsetY = halfAfter;
        } else {
          cpStart.x = cp1.x - fRadius1 + halfBefore;
          cpStart.y = cp1.y;
          offsetEX = -halfAfter;
        }
        vx = -1;
        vy = -1;
        nx = 1;
        ny = 0;
        if (bRound) {
          sx = bInverted ? FX_PI * 3 / 2 : 0;
        } else {
          sx = -1;
          sy = 0;
        }
        break;
      case 6:
      case 7:
        cp1 = rtWidget.BottomLeft();
        cp2 = rtWidget.TopLeft();
        if (nIndex == 6) {
          cpStart.x = cp1.x + fRadius1;
          cpStart.y = cp1.y + halfBefore;
          offsetX = -halfAfter;
        } else {
          cpStart.x = cp1.x;
          cpStart.y = cp1.y - fRadius1 + halfBefore;
          offsetEY = -halfAfter;
        }
        vx = 1;
        vy = -1;
        nx = 0;
        ny = 1;
        if (bRound) {
          sx = bInverted ? 0 : FX_PI / 2;
        } else {
          sx = 0;
          sy = -1;
        }
        break;
    }
    if (bStart) {
      path.MoveTo(cpStart);
    }
    if (nIndex & 1) {
      path.LineTo(CFX_PointF(cp2.x + fRadius2 * nx + offsetEX,
                             cp2.y + fRadius2 * ny + offsetEY));
      return;
    }
    if (bRound) {
      if (fRadius1 < 0)
        sx -= FX_PI;
      if (bInverted)
        sy *= -1;
  
      CFX_RectF rtRadius(cp1.x + offsetX * 2, cp1.y + offsetY * 2,
                         fRadius1 * 2 * vx - offsetX * 2,
                         fRadius1 * 2 * vy - offsetY * 2);
      rtRadius.Normalize();
      if (bInverted)
        rtRadius.Offset(-fRadius1 * vx, -fRadius1 * vy);
  
      path.ArcTo(rtRadius.TopLeft(), rtRadius.Size(), sx, sy);
    } else {
      CFX_PointF cp;
      if (bInverted) {
        cp.x = cp1.x + fRadius1 * vx;
        cp.y = cp1.y + fRadius1 * vy;
      } else {
        cp = cp1;
      }
      path.LineTo(cp);
      path.LineTo(CFX_PointF(cp1.x + fRadius1 * sx + offsetX,
                             cp1.y + fRadius1 * sy + offsetY));
    }
  }
  