// Copyright 2014 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/fxgraphics/cfx_graphics.h"

#include <memory>

#include "core/fxge/cfx_fxgedevice.h"
#include "core/fxge/cfx_gemodule.h"
#include "core/fxge/cfx_renderdevice.h"
#include "core/fxge/cfx_unicodeencoding.h"
#include "third_party/base/ptr_util.h"
#include "xfa/fxgraphics/cfx_color.h"
#include "xfa/fxgraphics/cfx_path.h"
#include "xfa/fxgraphics/cfx_pattern.h"
#include "xfa/fxgraphics/cfx_shading.h"

namespace {

enum {
  FX_CONTEXT_None = 0,
  FX_CONTEXT_Device,
};

#define FX_HATCHSTYLE_Total 53

struct FX_HATCHDATA {
  int32_t width;
  int32_t height;
  uint8_t maskBits[64];
};

const FX_HATCHDATA hatchBitmapData[FX_HATCHSTYLE_Total] = {
    {16,  // Horizontal
     16,
     {
         0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
         0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
         0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff,
         0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
         0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
         0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
     }},
    {16,  // Vertical
     16,
     {
         0x80, 0x80, 0x00, 0x00, 0x80, 0x80, 0x00, 0x00, 0x80, 0x80, 0x00,
         0x00, 0x80, 0x80, 0x00, 0x00, 0x80, 0x80, 0x00, 0x00, 0x80, 0x80,
         0x00, 0x00, 0x80, 0x80, 0x00, 0x00, 0x80, 0x80, 0x00, 0x00, 0x80,
         0x80, 0x00, 0x00, 0x80, 0x80, 0x00, 0x00, 0x80, 0x80, 0x00, 0x00,
         0x80, 0x80, 0x00, 0x00, 0x80, 0x80, 0x00, 0x00, 0x80, 0x80, 0x00,
         0x00, 0x80, 0x80, 0x00, 0x00, 0x80, 0x80, 0x00, 0x00,
     }},
    {16,  // ForwardDiagonal
     16,
     {
         0x80, 0x80, 0x00, 0x00, 0x40, 0x40, 0x00, 0x00, 0x20, 0x20, 0x00,
         0x00, 0x10, 0x10, 0x00, 0x00, 0x08, 0x08, 0x00, 0x00, 0x04, 0x04,
         0x00, 0x00, 0x02, 0x02, 0x00, 0x00, 0x01, 0x01, 0x00, 0x00, 0x80,
         0x80, 0x00, 0x00, 0x40, 0x40, 0x00, 0x00, 0x20, 0x20, 0x00, 0x00,
         0x10, 0x10, 0x00, 0x00, 0x08, 0x08, 0x00, 0x00, 0x04, 0x04, 0x00,
         0x00, 0x02, 0x02, 0x00, 0x00, 0x01, 0x01, 0x00, 0x00,
     }},
    {16,  // BackwardDiagonal
     16,
     {
         0x01, 0x01, 0x00, 0x00, 0x02, 0x02, 0x00, 0x00, 0x04, 0x04, 0x00,
         0x00, 0x08, 0x08, 0x00, 0x00, 0x10, 0x10, 0x00, 0x00, 0x20, 0x20,
         0x00, 0x00, 0x40, 0x40, 0x00, 0x00, 0x80, 0x80, 0x00, 0x00, 0x01,
         0x01, 0x00, 0x00, 0x02, 0x02, 0x00, 0x00, 0x04, 0x04, 0x00, 0x00,
         0x08, 0x08, 0x00, 0x00, 0x10, 0x10, 0x00, 0x00, 0x20, 0x20, 0x00,
         0x00, 0x40, 0x40, 0x00, 0x00, 0x80, 0x80, 0x00, 0x00,
     }},
    {16,  // Cross
     16,
     {
         0xff, 0xff, 0x00, 0x00, 0x80, 0x80, 0x00, 0x00, 0x80, 0x80, 0x00,
         0x00, 0x80, 0x80, 0x00, 0x00, 0x80, 0x80, 0x00, 0x00, 0x80, 0x80,
         0x00, 0x00, 0x80, 0x80, 0x00, 0x00, 0x80, 0x80, 0x00, 0x00, 0xff,
         0xff, 0x00, 0x00, 0x80, 0x80, 0x00, 0x00, 0x80, 0x80, 0x00, 0x00,
         0x80, 0x80, 0x00, 0x00, 0x80, 0x80, 0x00, 0x00, 0x80, 0x80, 0x00,
         0x00, 0x80, 0x80, 0x00, 0x00, 0x80, 0x80, 0x00, 0x00,
     }},
    {16,  // DiagonalCross
     16,
     {
         0x81, 0x81, 0x00, 0x00, 0x42, 0x42, 0x00, 0x00, 0x24, 0x24, 0x00,
         0x00, 0x18, 0x18, 0x00, 0x00, 0x18, 0x18, 0x00, 0x00, 0x24, 0x24,
         0x00, 0x00, 0x42, 0x42, 0x00, 0x00, 0x81, 0x81, 0x00, 0x00, 0x81,
         0x81, 0x00, 0x00, 0x42, 0x42, 0x00, 0x00, 0x24, 0x24, 0x00, 0x00,
         0x18, 0x18, 0x00, 0x00, 0x18, 0x18, 0x00, 0x00, 0x24, 0x24, 0x00,
         0x00, 0x42, 0x42, 0x00, 0x00, 0x81, 0x81, 0x00, 0x00,
     }},
};

}  // namespace

CFX_Graphics::CFX_Graphics(CFX_RenderDevice* renderDevice)
    : m_type(FX_CONTEXT_None), m_renderDevice(renderDevice) {
  if (!renderDevice)
    return;
  m_type = FX_CONTEXT_Device;
}

CFX_Graphics::~CFX_Graphics() {}

void CFX_Graphics::SaveGraphState() {
  if (m_type != FX_CONTEXT_Device || !m_renderDevice)
    return;

  m_renderDevice->SaveState();
  m_infoStack.push_back(pdfium::MakeUnique<TInfo>(m_info));
}

void CFX_Graphics::RestoreGraphState() {
  if (m_type != FX_CONTEXT_Device || !m_renderDevice)
    return;

  m_renderDevice->RestoreState(false);
  if (m_infoStack.empty() || !m_infoStack.back())
    return;

  m_info = *m_infoStack.back();
  m_infoStack.pop_back();
  return;
}

void CFX_Graphics::SetLineCap(CFX_GraphStateData::LineCap lineCap) {
  if (m_type == FX_CONTEXT_Device && m_renderDevice) {
    m_info.graphState.m_LineCap = lineCap;
  }
}

void CFX_Graphics::SetLineDash(float dashPhase,
                               float* dashArray,
                               int32_t dashCount) {
  if (dashCount > 0 && !dashArray)
    return;

  dashCount = dashCount < 0 ? 0 : dashCount;
  if (m_type == FX_CONTEXT_Device && m_renderDevice) {
    float scale = 1.0;
    if (m_info.isActOnDash) {
      scale = m_info.graphState.m_LineWidth;
    }
    m_info.graphState.m_DashPhase = dashPhase;
    m_info.graphState.SetDashCount(dashCount);
    for (int32_t i = 0; i < dashCount; i++) {
      m_info.graphState.m_DashArray[i] = dashArray[i] * scale;
    }
  }
}

void CFX_Graphics::SetLineDash(FX_DashStyle dashStyle) {
  if (m_type == FX_CONTEXT_Device && m_renderDevice)
    RenderDeviceSetLineDash(dashStyle);
}

void CFX_Graphics::SetLineWidth(float lineWidth, bool isActOnDash) {
  if (m_type == FX_CONTEXT_Device && m_renderDevice) {
    m_info.graphState.m_LineWidth = lineWidth;
    m_info.isActOnDash = isActOnDash;
  }
}

void CFX_Graphics::SetStrokeColor(CFX_Color* color) {
  if (!color)
    return;
  if (m_type == FX_CONTEXT_Device && m_renderDevice) {
    m_info.strokeColor = color;
  }
}

void CFX_Graphics::SetFillColor(CFX_Color* color) {
  if (!color)
    return;
  if (m_type == FX_CONTEXT_Device && m_renderDevice) {
    m_info.fillColor = color;
  }
}

void CFX_Graphics::StrokePath(CFX_Path* path, CFX_Matrix* matrix) {
  if (!path)
    return;
  if (m_type == FX_CONTEXT_Device && m_renderDevice)
    RenderDeviceStrokePath(path, matrix);
}

void CFX_Graphics::FillPath(CFX_Path* path,
                            FX_FillMode fillMode,
                            CFX_Matrix* matrix) {
  if (!path)
    return;
  if (m_type == FX_CONTEXT_Device && m_renderDevice)
    RenderDeviceFillPath(path, fillMode, matrix);
}

void CFX_Graphics::StretchImage(const CFX_RetainPtr<CFX_DIBSource>& source,
                                const CFX_RectF& rect,
                                CFX_Matrix* matrix) {
  if (!source)
    return;
  if (m_type == FX_CONTEXT_Device && m_renderDevice)
    RenderDeviceStretchImage(source, rect, matrix);
}

void CFX_Graphics::ConcatMatrix(const CFX_Matrix* matrix) {
  if (!matrix)
    return;
  if (m_type == FX_CONTEXT_Device && m_renderDevice) {
    m_info.CTM.Concat(*matrix);
  }
}

CFX_Matrix* CFX_Graphics::GetMatrix() {
  if (m_type == FX_CONTEXT_Device && m_renderDevice)
    return &m_info.CTM;
  return nullptr;
}

CFX_RectF CFX_Graphics::GetClipRect() const {
  if (m_type != FX_CONTEXT_Device || !m_renderDevice)
    return CFX_RectF();

  FX_RECT r = m_renderDevice->GetClipBox();
  return CFX_Rect(r.left, r.top, r.Width(), r.Height()).As<float>();
}

void CFX_Graphics::SetClipRect(const CFX_RectF& rect) {
  if (m_type == FX_CONTEXT_Device && m_renderDevice) {
    m_renderDevice->SetClip_Rect(
        FX_RECT(FXSYS_round(rect.left), FXSYS_round(rect.top),
                FXSYS_round(rect.right()), FXSYS_round(rect.bottom())));
  }
}

CFX_RenderDevice* CFX_Graphics::GetRenderDevice() {
  return m_renderDevice;
}

void CFX_Graphics::RenderDeviceSetLineDash(FX_DashStyle dashStyle) {
  switch (dashStyle) {
    case FX_DASHSTYLE_Solid: {
      m_info.graphState.SetDashCount(0);
      return;
    }
    case FX_DASHSTYLE_Dash: {
      float dashArray[] = {3, 1};
      SetLineDash(0, dashArray, 2);
      return;
    }
    case FX_DASHSTYLE_Dot: {
      float dashArray[] = {1, 1};
      SetLineDash(0, dashArray, 2);
      return;
    }
    case FX_DASHSTYLE_DashDot: {
      float dashArray[] = {3, 1, 1, 1};
      SetLineDash(0, dashArray, 4);
      return;
    }
    case FX_DASHSTYLE_DashDotDot: {
      float dashArray[] = {4, 1, 2, 1, 2, 1};
      SetLineDash(0, dashArray, 6);
      return;
    }
    default:
      return;
  }
}

void CFX_Graphics::RenderDeviceStrokePath(CFX_Path* path, CFX_Matrix* matrix) {
  if (!m_info.strokeColor)
    return;
  CFX_Matrix m(m_info.CTM.a, m_info.CTM.b, m_info.CTM.c, m_info.CTM.d,
               m_info.CTM.e, m_info.CTM.f);
  if (matrix) {
    m.Concat(*matrix);
  }
  switch (m_info.strokeColor->m_type) {
    case FX_COLOR_Solid: {
      m_renderDevice->DrawPath(path->GetPathData(), &m, &m_info.graphState, 0x0,
                               m_info.strokeColor->m_info.argb, 0);
      return;
    }
    default:
      return;
  }
}

void CFX_Graphics::RenderDeviceFillPath(CFX_Path* path,
                                        FX_FillMode fillMode,
                                        CFX_Matrix* matrix) {
  if (!m_info.fillColor)
    return;
  CFX_Matrix m(m_info.CTM.a, m_info.CTM.b, m_info.CTM.c, m_info.CTM.d,
               m_info.CTM.e, m_info.CTM.f);
  if (matrix) {
    m.Concat(*matrix);
  }
  switch (m_info.fillColor->m_type) {
    case FX_COLOR_Solid: {
      m_renderDevice->DrawPath(path->GetPathData(), &m, &m_info.graphState,
                               m_info.fillColor->m_info.argb, 0x0, fillMode);
      return;
    }
    case FX_COLOR_Pattern:
      FillPathWithPattern(path, fillMode, &m);
      return;
    case FX_COLOR_Shading:
      FillPathWithShading(path, fillMode, &m);
      return;
    default:
      return;
  }
}

void CFX_Graphics::RenderDeviceStretchImage(
    const CFX_RetainPtr<CFX_DIBSource>& source,
    const CFX_RectF& rect,
    CFX_Matrix* matrix) {
  CFX_Matrix m1(m_info.CTM.a, m_info.CTM.b, m_info.CTM.c, m_info.CTM.d,
                m_info.CTM.e, m_info.CTM.f);
  if (matrix) {
    m1.Concat(*matrix);
  }
  CFX_RetainPtr<CFX_DIBitmap> bmp1 =
      source->StretchTo((int32_t)rect.Width(), (int32_t)rect.Height());
  CFX_Matrix m2(rect.Width(), 0.0, 0.0, rect.Height(), rect.left, rect.top);
  m2.Concat(m1);

  int32_t left;
  int32_t top;
  CFX_RetainPtr<CFX_DIBitmap> bmp2 = bmp1->FlipImage(false, true);
  CFX_RetainPtr<CFX_DIBitmap> bmp3 = bmp2->TransformTo(&m2, left, top);
  CFX_RectF r = GetClipRect();
  CFX_RetainPtr<CFX_DIBitmap> bitmap = m_renderDevice->GetBitmap();
  bitmap->CompositeBitmap(FXSYS_round(r.left), FXSYS_round(r.top),
                          FXSYS_round(r.Width()), FXSYS_round(r.Height()), bmp3,
                          FXSYS_round(r.left - left), FXSYS_round(r.top - top));
}

void CFX_Graphics::FillPathWithPattern(CFX_Path* path,
                                       FX_FillMode fillMode,
                                       CFX_Matrix* matrix) {
  CFX_Pattern* pattern = m_info.fillColor->m_info.pattern;
  CFX_RetainPtr<CFX_DIBitmap> bitmap = m_renderDevice->GetBitmap();
  int32_t width = bitmap->GetWidth();
  int32_t height = bitmap->GetHeight();
  auto bmp = pdfium::MakeRetain<CFX_DIBitmap>();
  bmp->Create(width, height, FXDIB_Argb);
  m_renderDevice->GetDIBits(bmp, 0, 0);

  FX_HatchStyle hatchStyle = m_info.fillColor->m_info.pattern->m_hatchStyle;
  const FX_HATCHDATA& data = hatchBitmapData[static_cast<int>(hatchStyle)];

  auto mask = pdfium::MakeRetain<CFX_DIBitmap>();
  mask->Create(data.width, data.height, FXDIB_1bppMask);
  memcpy(mask->GetBuffer(), data.maskBits, mask->GetPitch() * data.height);
  CFX_FloatRect rectf = path->GetPathData()->GetBoundingBox();
  if (matrix)
    matrix->TransformRect(rectf);

  FX_RECT rect(FXSYS_round(rectf.left), FXSYS_round(rectf.top),
               FXSYS_round(rectf.right), FXSYS_round(rectf.bottom));
  CFX_FxgeDevice device;
  device.Attach(bmp, false, nullptr, false);
  device.FillRect(&rect, m_info.fillColor->m_info.pattern->m_backArgb);
  for (int32_t j = rect.bottom; j < rect.top; j += mask->GetHeight()) {
    for (int32_t i = rect.left; i < rect.right; i += mask->GetWidth()) {
      device.SetBitMask(mask, i, j,
                        m_info.fillColor->m_info.pattern->m_foreArgb);
    }
  }

  m_renderDevice->SaveState();
  m_renderDevice->SetClip_PathFill(path->GetPathData(), matrix, fillMode);
  SetDIBitsWithMatrix(bmp, &pattern->m_matrix);
  m_renderDevice->RestoreState(false);
}

void CFX_Graphics::FillPathWithShading(CFX_Path* path,
                                       FX_FillMode fillMode,
                                       CFX_Matrix* matrix) {
  CFX_RetainPtr<CFX_DIBitmap> bitmap = m_renderDevice->GetBitmap();
  int32_t width = bitmap->GetWidth();
  int32_t height = bitmap->GetHeight();
  float start_x = m_info.fillColor->m_shading->m_beginPoint.x;
  float start_y = m_info.fillColor->m_shading->m_beginPoint.y;
  float end_x = m_info.fillColor->m_shading->m_endPoint.x;
  float end_y = m_info.fillColor->m_shading->m_endPoint.y;
  auto bmp = pdfium::MakeRetain<CFX_DIBitmap>();
  bmp->Create(width, height, FXDIB_Argb);
  m_renderDevice->GetDIBits(bmp, 0, 0);
  int32_t pitch = bmp->GetPitch();
  bool result = false;
  switch (m_info.fillColor->m_shading->m_type) {
    case FX_SHADING_Axial: {
      float x_span = end_x - start_x;
      float y_span = end_y - start_y;
      float axis_len_square = (x_span * x_span) + (y_span * y_span);
      for (int32_t row = 0; row < height; row++) {
        uint32_t* dib_buf = (uint32_t*)(bmp->GetBuffer() + row * pitch);
        for (int32_t column = 0; column < width; column++) {
          float x = (float)(column);
          float y = (float)(row);
          float scale = (((x - start_x) * x_span) + ((y - start_y) * y_span)) /
                        axis_len_square;
          if (scale < 0) {
            if (!m_info.fillColor->m_shading->m_isExtendedBegin) {
              continue;
            }
            scale = 0;
          } else if (scale > 1.0f) {
            if (!m_info.fillColor->m_shading->m_isExtendedEnd) {
              continue;
            }
            scale = 1.0f;
          }
          int32_t index = (int32_t)(scale * (FX_SHADING_Steps - 1));
          dib_buf[column] = m_info.fillColor->m_shading->m_argbArray[index];
        }
      }
      result = true;
      break;
    }
    case FX_SHADING_Radial: {
      float start_r = m_info.fillColor->m_shading->m_beginRadius;
      float end_r = m_info.fillColor->m_shading->m_endRadius;
      float a = ((start_x - end_x) * (start_x - end_x)) +
                ((start_y - end_y) * (start_y - end_y)) -
                ((start_r - end_r) * (start_r - end_r));
      for (int32_t row = 0; row < height; row++) {
        uint32_t* dib_buf = (uint32_t*)(bmp->GetBuffer() + row * pitch);
        for (int32_t column = 0; column < width; column++) {
          float x = (float)(column);
          float y = (float)(row);
          float b = -2 * (((x - start_x) * (end_x - start_x)) +
                          ((y - start_y) * (end_y - start_y)) +
                          (start_r * (end_r - start_r)));
          float c = ((x - start_x) * (x - start_x)) +
                    ((y - start_y) * (y - start_y)) - (start_r * start_r);
          float s;
          if (a == 0) {
            s = -c / b;
          } else {
            float b2_4ac = (b * b) - 4 * (a * c);
            if (b2_4ac < 0) {
              continue;
            }
            float root = (sqrt(b2_4ac));
            float s1, s2;
            if (a > 0) {
              s1 = (-b - root) / (2 * a);
              s2 = (-b + root) / (2 * a);
            } else {
              s2 = (-b - root) / (2 * a);
              s1 = (-b + root) / (2 * a);
            }
            if (s2 <= 1.0f || m_info.fillColor->m_shading->m_isExtendedEnd) {
              s = (s2);
            } else {
              s = (s1);
            }
            if ((start_r) + s * (end_r - start_r) < 0) {
              continue;
            }
          }
          if (s < 0) {
            if (!m_info.fillColor->m_shading->m_isExtendedBegin) {
              continue;
            }
            s = 0;
          }
          if (s > 1.0f) {
            if (!m_info.fillColor->m_shading->m_isExtendedEnd) {
              continue;
            }
            s = 1.0f;
          }
          int index = (int32_t)(s * (FX_SHADING_Steps - 1));
          dib_buf[column] = m_info.fillColor->m_shading->m_argbArray[index];
        }
      }
      result = true;
      break;
    }
    default: {
      result = false;
      break;
    }
  }
  if (result) {
    m_renderDevice->SaveState();
    m_renderDevice->SetClip_PathFill(path->GetPathData(), matrix, fillMode);
    SetDIBitsWithMatrix(bmp, matrix);
    m_renderDevice->RestoreState(false);
  }
}

void CFX_Graphics::SetDIBitsWithMatrix(
    const CFX_RetainPtr<CFX_DIBSource>& source,
    CFX_Matrix* matrix) {
  if (matrix->IsIdentity()) {
    m_renderDevice->SetDIBits(source, 0, 0);
  } else {
    CFX_Matrix m((float)source->GetWidth(), 0, 0, (float)source->GetHeight(), 0,
                 0);
    m.Concat(*matrix);
    int32_t left;
    int32_t top;
    CFX_RetainPtr<CFX_DIBitmap> bmp1 = source->FlipImage(false, true);
    CFX_RetainPtr<CFX_DIBitmap> bmp2 = bmp1->TransformTo(&m, left, top);
    m_renderDevice->SetDIBits(bmp2, left, top);
  }
}

CFX_Graphics::TInfo::TInfo()
    : isActOnDash(false), strokeColor(nullptr), fillColor(nullptr) {}

CFX_Graphics::TInfo::TInfo(const TInfo& info)
    : graphState(info.graphState),
      CTM(info.CTM),
      isActOnDash(info.isActOnDash),
      strokeColor(info.strokeColor),
      fillColor(info.fillColor) {}

CFX_Graphics::TInfo& CFX_Graphics::TInfo::operator=(const TInfo& other) {
  graphState.Copy(other.graphState);
  CTM = other.CTM;
  isActOnDash = other.isActOnDash;
  strokeColor = other.strokeColor;
  fillColor = other.fillColor;
  return *this;
}
