// 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/include/fxgraphics/fx_graphics.h"

#include <memory>

#include "xfa/fxgraphics/fx_path_generator.h"
#include "xfa/fxgraphics/pre.h"

class CAGG_Graphics {
 public:
  CAGG_Graphics();
  FX_ERR Create(CFX_Graphics* owner,
                int32_t width,
                int32_t height,
                FXDIB_Format format);
  virtual ~CAGG_Graphics();

 private:
  CFX_Graphics* _owner;
};
CFX_Graphics::CFX_Graphics() {
  _type = FX_CONTEXT_None;
  _info._graphState.SetDashCount(0);
  _info._isAntialiasing = TRUE;
  _info._strokeAlignment = FX_STROKEALIGNMENT_Center;
  _info._CTM.SetIdentity();
  _info._isActOnDash = FALSE;
  _info._strokeColor = NULL;
  _info._fillColor = NULL;
  _info._font = NULL;
  _info._fontSize = 40.0;
  _info._fontHScale = 1.0;
  _info._fontSpacing = 0.0;
  _renderDevice = NULL;
  _aggGraphics = NULL;
}
FX_ERR CFX_Graphics::Create(CFX_RenderDevice* renderDevice,
                            FX_BOOL isAntialiasing) {
  if (!renderDevice)
    return FX_ERR_Parameter_Invalid;
  if (_type != FX_CONTEXT_None) {
    return FX_ERR_Property_Invalid;
  }
  _type = FX_CONTEXT_Device;
  _info._isAntialiasing = isAntialiasing;
  _renderDevice = renderDevice;
  if (_renderDevice->GetDeviceCaps(FXDC_RENDER_CAPS) & FXRC_SOFT_CLIP) {
    return FX_ERR_Succeeded;
  }
  return FX_ERR_Indefinite;
}
FX_ERR CFX_Graphics::Create(int32_t width,
                            int32_t height,
                            FXDIB_Format format,
                            FX_BOOL isNative,
                            FX_BOOL isAntialiasing) {
  if (_type != FX_CONTEXT_None) {
    return FX_ERR_Property_Invalid;
  }
  _type = FX_CONTEXT_Device;
  _info._isAntialiasing = isAntialiasing;
  {
    _aggGraphics = new CAGG_Graphics;
    return _aggGraphics->Create(this, width, height, format);
  }
}
CFX_Graphics::~CFX_Graphics() {
  if (_aggGraphics) {
    delete _aggGraphics;
    _aggGraphics = NULL;
  }
  _renderDevice = NULL;
  _info._graphState.SetDashCount(0);
  _type = FX_CONTEXT_None;
}
FX_ERR CFX_Graphics::GetDeviceCap(const int32_t capID, FX_DeviceCap& capVal) {
  switch (_type) {
    case FX_CONTEXT_Device: {
      if (!_renderDevice)
        return FX_ERR_Property_Invalid;
      capVal = _renderDevice->GetDeviceCaps(capID);
      return FX_ERR_Succeeded;
    }
    default: { return FX_ERR_Property_Invalid; }
  }
}
FX_ERR CFX_Graphics::IsPrinterDevice(FX_BOOL& isPrinter) {
  switch (_type) {
    case FX_CONTEXT_Device: {
      if (!_renderDevice)
        return FX_ERR_Property_Invalid;
      int32_t deviceClass = _renderDevice->GetDeviceClass();
      if (deviceClass == FXDC_PRINTER) {
        isPrinter = TRUE;
      } else {
        isPrinter = FALSE;
      }
      return FX_ERR_Succeeded;
    }
    default: { return FX_ERR_Property_Invalid; }
  }
}
FX_ERR CFX_Graphics::EnableAntialiasing(FX_BOOL isAntialiasing) {
  switch (_type) {
    case FX_CONTEXT_Device: {
      if (!_renderDevice)
        return FX_ERR_Property_Invalid;
      _info._isAntialiasing = isAntialiasing;
      return FX_ERR_Succeeded;
    }
    default: { return FX_ERR_Property_Invalid; }
  }
}
FX_ERR CFX_Graphics::SaveGraphState() {
  switch (_type) {
    case FX_CONTEXT_Device: {
      if (!_renderDevice)
        return FX_ERR_Property_Invalid;
      _renderDevice->SaveState();
      TInfo* info = new TInfo;
      info->_graphState.Copy(_info._graphState);
      info->_isAntialiasing = _info._isAntialiasing;
      info->_strokeAlignment = _info._strokeAlignment;
      info->_CTM = _info._CTM;
      info->_isActOnDash = _info._isActOnDash;
      info->_strokeColor = _info._strokeColor;
      info->_fillColor = _info._fillColor;
      info->_font = _info._font;
      info->_fontSize = _info._fontSize;
      info->_fontHScale = _info._fontHScale;
      info->_fontSpacing = _info._fontSpacing;
      _infoStack.Add(info);
      return FX_ERR_Succeeded;
    }
    default: { return FX_ERR_Property_Invalid; }
  }
}
FX_ERR CFX_Graphics::RestoreGraphState() {
  switch (_type) {
    case FX_CONTEXT_Device: {
      if (!_renderDevice)
        return FX_ERR_Property_Invalid;
      _renderDevice->RestoreState();
      int32_t size = _infoStack.GetSize();
      if (size <= 0) {
        return FX_ERR_Intermediate_Value_Invalid;
      }
      int32_t topIndex = size - 1;
      TInfo* info = (TInfo*)_infoStack.GetAt(topIndex);
      if (!info)
        return FX_ERR_Intermediate_Value_Invalid;
      _info._graphState.Copy(info->_graphState);
      _info._isAntialiasing = info->_isAntialiasing;
      _info._strokeAlignment = info->_strokeAlignment;
      _info._CTM = info->_CTM;
      _info._isActOnDash = info->_isActOnDash;
      _info._strokeColor = info->_strokeColor;
      _info._fillColor = info->_fillColor;
      _info._font = info->_font;
      _info._fontSize = info->_fontSize;
      _info._fontHScale = info->_fontHScale;
      _info._fontSpacing = info->_fontSpacing;
      delete info;
      info = NULL;
      _infoStack.RemoveAt(topIndex);
      return FX_ERR_Succeeded;
    }
    default: { return FX_ERR_Property_Invalid; }
  }
}
FX_ERR CFX_Graphics::GetLineCap(CFX_GraphStateData::LineCap& lineCap) {
  switch (_type) {
    case FX_CONTEXT_Device: {
      if (!_renderDevice)
        return FX_ERR_Property_Invalid;
      lineCap = _info._graphState.m_LineCap;
      return FX_ERR_Succeeded;
    }
    default: { return FX_ERR_Property_Invalid; }
  }
}
FX_ERR CFX_Graphics::SetLineCap(CFX_GraphStateData::LineCap lineCap) {
  switch (_type) {
    case FX_CONTEXT_Device: {
      if (!_renderDevice)
        return FX_ERR_Property_Invalid;
      _info._graphState.m_LineCap = lineCap;
      return FX_ERR_Succeeded;
    }
    default: { return FX_ERR_Property_Invalid; }
  }
}
FX_ERR CFX_Graphics::GetDashCount(int32_t& dashCount) {
  switch (_type) {
    case FX_CONTEXT_Device: {
      if (!_renderDevice)
        return FX_ERR_Property_Invalid;
      dashCount = _info._graphState.m_DashCount;
      return FX_ERR_Succeeded;
    }
    default: { return FX_ERR_Property_Invalid; }
  }
}
FX_ERR CFX_Graphics::GetLineDash(FX_FLOAT& dashPhase, FX_FLOAT* dashArray) {
  if (!dashArray)
    return FX_ERR_Parameter_Invalid;
  switch (_type) {
    case FX_CONTEXT_Device: {
      if (!_renderDevice)
        return FX_ERR_Property_Invalid;
      dashPhase = _info._graphState.m_DashPhase;
      FXSYS_memcpy(dashArray, _info._graphState.m_DashArray,
                   _info._graphState.m_DashCount * sizeof(FX_FLOAT));
      return FX_ERR_Succeeded;
    }
    default: { return FX_ERR_Property_Invalid; }
  }
}
FX_ERR CFX_Graphics::SetLineDash(FX_FLOAT dashPhase,
                                 FX_FLOAT* dashArray,
                                 int32_t dashCount) {
  if (dashCount > 0 && !dashArray) {
    return FX_ERR_Parameter_Invalid;
  }
  dashCount = dashCount < 0 ? 0 : dashCount;
  switch (_type) {
    case FX_CONTEXT_Device: {
      if (!_renderDevice)
        return FX_ERR_Property_Invalid;
      FX_FLOAT scale = 1.0;
      if (_info._isActOnDash) {
        scale = _info._graphState.m_LineWidth;
      }
      _info._graphState.m_DashPhase = dashPhase;
      _info._graphState.SetDashCount(dashCount);
      for (int32_t i = 0; i < dashCount; i++) {
        _info._graphState.m_DashArray[i] = dashArray[i] * scale;
      }
      return FX_ERR_Succeeded;
    }
    default: { return FX_ERR_Property_Invalid; }
  }
}
FX_ERR CFX_Graphics::SetLineDash(FX_DashStyle dashStyle) {
  switch (_type) {
    case FX_CONTEXT_Device: {
      if (!_renderDevice)
        return FX_ERR_Property_Invalid;
      return RenderDeviceSetLineDash(dashStyle);
    }
    default: { return FX_ERR_Property_Invalid; }
  }
}
FX_ERR CFX_Graphics::GetLineJoin(CFX_GraphStateData::LineJoin& lineJoin) {
  switch (_type) {
    case FX_CONTEXT_Device: {
      if (!_renderDevice)
        return FX_ERR_Property_Invalid;
      lineJoin = _info._graphState.m_LineJoin;
      return FX_ERR_Succeeded;
    }
    default: { return FX_ERR_Property_Invalid; }
  }
}
FX_ERR CFX_Graphics::SetLineJoin(CFX_GraphStateData::LineJoin lineJoin) {
  switch (_type) {
    case FX_CONTEXT_Device: {
      if (!_renderDevice)
        return FX_ERR_Property_Invalid;
      _info._graphState.m_LineJoin = lineJoin;
      return FX_ERR_Succeeded;
    }
    default: { return FX_ERR_Property_Invalid; }
  }
}
FX_ERR CFX_Graphics::GetMiterLimit(FX_FLOAT& miterLimit) {
  switch (_type) {
    case FX_CONTEXT_Device: {
      if (!_renderDevice)
        return FX_ERR_Property_Invalid;
      miterLimit = _info._graphState.m_MiterLimit;
      return FX_ERR_Succeeded;
    }
    default: { return FX_ERR_Property_Invalid; }
  }
}
FX_ERR CFX_Graphics::SetMiterLimit(FX_FLOAT miterLimit) {
  switch (_type) {
    case FX_CONTEXT_Device: {
      if (!_renderDevice)
        return FX_ERR_Property_Invalid;
      _info._graphState.m_MiterLimit = miterLimit;
      return FX_ERR_Succeeded;
    }
    default: { return FX_ERR_Property_Invalid; }
  }
}
FX_ERR CFX_Graphics::GetLineWidth(FX_FLOAT& lineWidth) {
  switch (_type) {
    case FX_CONTEXT_Device: {
      if (!_renderDevice)
        return FX_ERR_Property_Invalid;
      lineWidth = _info._graphState.m_LineWidth;
      return FX_ERR_Succeeded;
    }
    default: { return FX_ERR_Property_Invalid; }
  }
}
FX_ERR CFX_Graphics::SetLineWidth(FX_FLOAT lineWidth, FX_BOOL isActOnDash) {
  switch (_type) {
    case FX_CONTEXT_Device: {
      if (!_renderDevice)
        return FX_ERR_Property_Invalid;
      _info._graphState.m_LineWidth = lineWidth;
      _info._isActOnDash = isActOnDash;
      return FX_ERR_Succeeded;
    }
    default: { return FX_ERR_Property_Invalid; }
  }
}
FX_ERR CFX_Graphics::GetStrokeAlignment(FX_StrokeAlignment& strokeAlignment) {
  switch (_type) {
    case FX_CONTEXT_Device: {
      if (!_renderDevice)
        return FX_ERR_Property_Invalid;
      strokeAlignment = _info._strokeAlignment;
      return FX_ERR_Succeeded;
    }
    default: { return FX_ERR_Property_Invalid; }
  }
}
FX_ERR CFX_Graphics::SetStrokeAlignment(FX_StrokeAlignment strokeAlignment) {
  switch (_type) {
    case FX_CONTEXT_Device: {
      if (!_renderDevice)
        return FX_ERR_Property_Invalid;
      _info._strokeAlignment = strokeAlignment;
      return FX_ERR_Succeeded;
    }
    default: { return FX_ERR_Property_Invalid; }
  }
}
FX_ERR CFX_Graphics::SetStrokeColor(CFX_Color* color) {
  if (!color)
    return FX_ERR_Parameter_Invalid;
  switch (_type) {
    case FX_CONTEXT_Device: {
      if (!_renderDevice)
        return FX_ERR_Property_Invalid;
      _info._strokeColor = color;
      return FX_ERR_Succeeded;
    }
    default: { return FX_ERR_Property_Invalid; }
  }
}
FX_ERR CFX_Graphics::SetFillColor(CFX_Color* color) {
  if (!color)
    return FX_ERR_Parameter_Invalid;
  switch (_type) {
    case FX_CONTEXT_Device: {
      if (!_renderDevice)
        return FX_ERR_Property_Invalid;
      _info._fillColor = color;
      return FX_ERR_Succeeded;
    }
    default: { return FX_ERR_Property_Invalid; }
  }
}
FX_ERR CFX_Graphics::StrokePath(CFX_Path* path, CFX_Matrix* matrix) {
  if (!path)
    return FX_ERR_Parameter_Invalid;
  switch (_type) {
    case FX_CONTEXT_Device: {
      if (!_renderDevice)
        return FX_ERR_Property_Invalid;
      return RenderDeviceStrokePath(path, matrix);
    }
    default: { return FX_ERR_Property_Invalid; }
  }
}
FX_ERR CFX_Graphics::FillPath(CFX_Path* path,
                              FX_FillMode fillMode,
                              CFX_Matrix* matrix) {
  if (!path)
    return FX_ERR_Parameter_Invalid;
  switch (_type) {
    case FX_CONTEXT_Device: {
      if (!_renderDevice)
        return FX_ERR_Property_Invalid;
      return RenderDeviceFillPath(path, fillMode, matrix);
    }
    default: { return FX_ERR_Property_Invalid; }
  }
}
FX_ERR CFX_Graphics::ClipPath(CFX_Path* path,
                              FX_FillMode fillMode,
                              CFX_Matrix* matrix) {
  if (!path)
    return FX_ERR_Parameter_Invalid;
  switch (_type) {
    case FX_CONTEXT_Device: {
      if (!_renderDevice)
        return FX_ERR_Property_Invalid;
      FX_BOOL result = _renderDevice->SetClip_PathFill(
          path->GetPathData(), (CFX_Matrix*)matrix, fillMode);
      if (!result)
        return FX_ERR_Indefinite;
      return FX_ERR_Succeeded;
    }
    default: { return FX_ERR_Property_Invalid; }
  }
}
FX_ERR CFX_Graphics::DrawImage(CFX_DIBSource* source,
                               const CFX_PointF& point,
                               CFX_Matrix* matrix) {
  if (!source)
    return FX_ERR_Parameter_Invalid;
  switch (_type) {
    case FX_CONTEXT_Device: {
      if (!_renderDevice)
        return FX_ERR_Property_Invalid;
      return RenderDeviceDrawImage(source, point, matrix);
    }
    default: { return FX_ERR_Property_Invalid; }
  }
}
FX_ERR CFX_Graphics::StretchImage(CFX_DIBSource* source,
                                  const CFX_RectF& rect,
                                  CFX_Matrix* matrix) {
  if (!source)
    return FX_ERR_Parameter_Invalid;
  switch (_type) {
    case FX_CONTEXT_Device: {
      if (!_renderDevice)
        return FX_ERR_Property_Invalid;
      return RenderDeviceStretchImage(source, rect, matrix);
    }
    default: { return FX_ERR_Property_Invalid; }
  }
}
FX_ERR CFX_Graphics::ConcatMatrix(const CFX_Matrix* matrix) {
  if (!matrix)
    return FX_ERR_Parameter_Invalid;
  switch (_type) {
    case FX_CONTEXT_Device: {
      if (!_renderDevice)
        return FX_ERR_Property_Invalid;
      _info._CTM.Concat(*matrix);
      return FX_ERR_Succeeded;
    }
    default: { return FX_ERR_Property_Invalid; }
  }
}
CFX_Matrix* CFX_Graphics::GetMatrix() {
  switch (_type) {
    case FX_CONTEXT_Device: {
      if (!_renderDevice)
        return NULL;
      return &_info._CTM;
    }
    default: { return NULL; }
  }
}
FX_ERR CFX_Graphics::GetClipRect(CFX_RectF& rect) {
  switch (_type) {
    case FX_CONTEXT_Device: {
      if (!_renderDevice)
        return FX_ERR_Property_Invalid;
      FX_RECT r = _renderDevice->GetClipBox();
      rect.left = (FX_FLOAT)r.left;
      rect.top = (FX_FLOAT)r.top;
      rect.width = (FX_FLOAT)r.Width();
      rect.height = (FX_FLOAT)r.Height();
      return FX_ERR_Succeeded;
    }
    default: { return FX_ERR_Property_Invalid; }
  }
}
FX_ERR CFX_Graphics::SetClipRect(const CFX_RectF& rect) {
  switch (_type) {
    case FX_CONTEXT_Device: {
      if (!_renderDevice)
        return FX_ERR_Property_Invalid;
      if (!_renderDevice->SetClip_Rect(
              FX_RECT(FXSYS_round(rect.left), FXSYS_round(rect.top),
                      FXSYS_round(rect.right()), FXSYS_round(rect.bottom())))) {
        return FX_ERR_Method_Not_Supported;
      }
      return FX_ERR_Succeeded;
    }
    default: { return FX_ERR_Property_Invalid; }
  }
}
FX_ERR CFX_Graphics::ClearClip() {
  switch (_type) {
    case FX_CONTEXT_Device: {
      if (!_renderDevice)
        return FX_ERR_Property_Invalid;
      return FX_ERR_Succeeded;
    }
    default: { return FX_ERR_Property_Invalid; }
  }
}
FX_ERR CFX_Graphics::SetFont(CFX_Font* font) {
  if (!font)
    return FX_ERR_Parameter_Invalid;
  switch (_type) {
    case FX_CONTEXT_Device: {
      if (!_renderDevice)
        return FX_ERR_Property_Invalid;
      _info._font = font;
      return FX_ERR_Succeeded;
    }
    default: { return FX_ERR_Property_Invalid; }
  }
}
FX_ERR CFX_Graphics::SetFontSize(const FX_FLOAT size) {
  FX_FLOAT fontSize = size <= 0 ? 1.0f : size;
  switch (_type) {
    case FX_CONTEXT_Device: {
      if (!_renderDevice)
        return FX_ERR_Property_Invalid;
      _info._fontSize = fontSize;
      return FX_ERR_Succeeded;
    }
    default: { return FX_ERR_Property_Invalid; }
  }
}
FX_ERR CFX_Graphics::SetFontHScale(const FX_FLOAT scale) {
  FX_FLOAT fontHScale = scale <= 0 ? 1.0f : scale;
  switch (_type) {
    case FX_CONTEXT_Device: {
      if (!_renderDevice)
        return FX_ERR_Property_Invalid;
      _info._fontHScale = fontHScale;
      return FX_ERR_Succeeded;
    }
    default: { return FX_ERR_Property_Invalid; }
  }
}
FX_ERR CFX_Graphics::SetCharSpacing(const FX_FLOAT spacing) {
  FX_FLOAT fontSpacing = spacing < 0 ? 0 : spacing;
  switch (_type) {
    case FX_CONTEXT_Device: {
      if (!_renderDevice)
        return FX_ERR_Property_Invalid;
      _info._fontSpacing = fontSpacing;
      return FX_ERR_Succeeded;
    }
    default: { return FX_ERR_Property_Invalid; }
  }
}
FX_ERR CFX_Graphics::SetTextDrawingMode(const int32_t mode) {
  switch (_type) {
    case FX_CONTEXT_Device: {
      if (!_renderDevice)
        return FX_ERR_Property_Invalid;
      return FX_ERR_Succeeded;
    }
    default: { return FX_ERR_Property_Invalid; }
  }
}
FX_ERR CFX_Graphics::ShowText(const CFX_PointF& point,
                              const CFX_WideString& text,
                              CFX_Matrix* matrix) {
  switch (_type) {
    case FX_CONTEXT_Device: {
      if (!_renderDevice)
        return FX_ERR_Property_Invalid;
      return RenderDeviceShowText(point, text, matrix);
    }
    default: { return FX_ERR_Property_Invalid; }
  }
}
FX_ERR CFX_Graphics::CalcTextRect(CFX_RectF& rect,
                                  const CFX_WideString& text,
                                  FX_BOOL isMultiline,
                                  CFX_Matrix* matrix) {
  switch (_type) {
    case FX_CONTEXT_Device: {
      if (!_renderDevice)
        return FX_ERR_Property_Invalid;
      int32_t length = text.GetLength();
      FX_DWORD* charCodes = FX_Alloc(FX_DWORD, length);
      FXTEXT_CHARPOS* charPos = FX_Alloc(FXTEXT_CHARPOS, length);
      CalcTextInfo(text, charCodes, charPos, rect);
      FX_Free(charPos);
      FX_Free(charCodes);
      return FX_ERR_Succeeded;
    }
    default: { return FX_ERR_Property_Invalid; }
  }
}
FX_ERR CFX_Graphics::Transfer(CFX_Graphics* graphics,
                              const CFX_Matrix* matrix) {
  if (!graphics)
    return FX_ERR_Parameter_Invalid;
  CFX_Matrix m;
  m.Set(_info._CTM.a, _info._CTM.b, _info._CTM.c, _info._CTM.d, _info._CTM.e,
        _info._CTM.f);
  if (matrix) {
    m.Concat(*matrix);
  }
  switch (_type) {
    case FX_CONTEXT_Device: {
      if (!_renderDevice)
        return FX_ERR_Property_Invalid;
      {
        if (!graphics->_renderDevice)
          return FX_ERR_Parameter_Invalid;
        CFX_DIBitmap* bitmap = graphics->_renderDevice->GetBitmap();
        FX_BOOL result = _renderDevice->SetDIBits(bitmap, 0, 0);
        if (!result)
          return FX_ERR_Method_Not_Supported;
      }
    }
    default: { return FX_ERR_Property_Invalid; }
  }
}
FX_ERR CFX_Graphics::Transfer(CFX_Graphics* graphics,
                              FX_FLOAT srcLeft,
                              FX_FLOAT srcTop,
                              const CFX_RectF& dstRect,
                              const CFX_Matrix* matrix) {
  if (!graphics)
    return FX_ERR_Parameter_Invalid;
  CFX_Matrix m;
  m.Set(_info._CTM.a, _info._CTM.b, _info._CTM.c, _info._CTM.d, _info._CTM.e,
        _info._CTM.f);
  if (matrix) {
    m.Concat(*matrix);
  }
  switch (_type) {
    case FX_CONTEXT_Device: {
      if (!_renderDevice)
        return FX_ERR_Property_Invalid;
      {
        if (!graphics->_renderDevice)
          return FX_ERR_Parameter_Invalid;
        CFX_DIBitmap* bitmap = graphics->_renderDevice->GetBitmap();
        CFX_DIBitmap bmp;
        FX_BOOL result =
            bmp.Create((int32_t)dstRect.width, (int32_t)dstRect.height,
                       bitmap->GetFormat());
        if (!result)
          return FX_ERR_Intermediate_Value_Invalid;
        result = graphics->_renderDevice->GetDIBits(&bmp, (int32_t)srcLeft,
                                                    (int32_t)srcTop);
        if (!result)
          return FX_ERR_Method_Not_Supported;
        result = _renderDevice->SetDIBits(&bmp, (int32_t)dstRect.left,
                                          (int32_t)dstRect.top);
        if (!result)
          return FX_ERR_Method_Not_Supported;
        return FX_ERR_Succeeded;
      }
    }
    default: { return FX_ERR_Property_Invalid; }
  }
}
CFX_RenderDevice* CFX_Graphics::GetRenderDevice() {
  return _renderDevice;
}
FX_ERR CFX_Graphics::InverseRect(const CFX_RectF& rect) {
  if (!_renderDevice)
    return FX_ERR_Property_Invalid;
  CFX_DIBitmap* bitmap = _renderDevice->GetBitmap();
  if (!bitmap)
    return FX_ERR_Property_Invalid;
  CFX_RectF temp(rect);
  _info._CTM.TransformRect(temp);
  CFX_RectF r;
  r.Set(0, 0, (FX_FLOAT)bitmap->GetWidth(), (FX_FLOAT)bitmap->GetWidth());
  r.Intersect(temp);
  if (r.IsEmpty()) {
    return FX_ERR_Parameter_Invalid;
  }
  FX_ARGB* pBuf =
      (FX_ARGB*)(bitmap->GetBuffer() + int32_t(r.top) * bitmap->GetPitch());
  int32_t bottom = (int32_t)r.bottom();
  int32_t right = (int32_t)r.right();
  for (int32_t i = (int32_t)r.top; i < bottom; i++) {
    FX_ARGB* pLine = pBuf + (int32_t)r.left;
    for (int32_t j = (int32_t)r.left; j < right; j++) {
      FX_ARGB c = *pLine;
      *pLine++ = (c & 0xFF000000) | (0xFFFFFF - (c & 0x00FFFFFF));
    }
    pBuf = (FX_ARGB*)((uint8_t*)pBuf + bitmap->GetPitch());
  }
  return FX_ERR_Succeeded;
}
FX_ERR CFX_Graphics::XorDIBitmap(const CFX_DIBitmap* srcBitmap,
                                 const CFX_RectF& rect) {
  if (!_renderDevice)
    return FX_ERR_Property_Invalid;
  CFX_DIBitmap* dst = _renderDevice->GetBitmap();
  if (!dst)
    return FX_ERR_Property_Invalid;
  CFX_RectF temp(rect);
  _info._CTM.TransformRect(temp);
  CFX_RectF r;
  r.Set(0, 0, (FX_FLOAT)dst->GetWidth(), (FX_FLOAT)dst->GetWidth());
  r.Intersect(temp);
  if (r.IsEmpty()) {
    return FX_ERR_Parameter_Invalid;
  }
  FX_ARGB* pSrcBuf = (FX_ARGB*)(srcBitmap->GetBuffer() +
                                int32_t(r.top) * srcBitmap->GetPitch());
  FX_ARGB* pDstBuf =
      (FX_ARGB*)(dst->GetBuffer() + int32_t(r.top) * dst->GetPitch());
  int32_t bottom = (int32_t)r.bottom();
  int32_t right = (int32_t)r.right();
  for (int32_t i = (int32_t)r.top; i < bottom; i++) {
    FX_ARGB* pSrcLine = pSrcBuf + (int32_t)r.left;
    FX_ARGB* pDstLine = pDstBuf + (int32_t)r.left;
    for (int32_t j = (int32_t)r.left; j < right; j++) {
      FX_ARGB c = *pDstLine;
      *pDstLine++ =
          ArgbEncode(FXARGB_A(c), (c & 0xFFFFFF) ^ (*pSrcLine & 0xFFFFFF));
      pSrcLine++;
    }
    pSrcBuf = (FX_ARGB*)((uint8_t*)pSrcBuf + srcBitmap->GetPitch());
    pDstBuf = (FX_ARGB*)((uint8_t*)pDstBuf + dst->GetPitch());
  }
  return FX_ERR_Succeeded;
}
FX_ERR CFX_Graphics::EqvDIBitmap(const CFX_DIBitmap* srcBitmap,
                                 const CFX_RectF& rect) {
  if (!_renderDevice)
    return FX_ERR_Property_Invalid;
  CFX_DIBitmap* dst = _renderDevice->GetBitmap();
  if (!dst)
    return FX_ERR_Property_Invalid;
  CFX_RectF temp(rect);
  _info._CTM.TransformRect(temp);
  CFX_RectF r;
  r.Set(0, 0, (FX_FLOAT)dst->GetWidth(), (FX_FLOAT)dst->GetWidth());
  r.Intersect(temp);
  if (r.IsEmpty()) {
    return FX_ERR_Parameter_Invalid;
  }
  FX_ARGB* pSrcBuf = (FX_ARGB*)(srcBitmap->GetBuffer() +
                                int32_t(r.top) * srcBitmap->GetPitch());
  FX_ARGB* pDstBuf =
      (FX_ARGB*)(dst->GetBuffer() + int32_t(r.top) * dst->GetPitch());
  int32_t bottom = (int32_t)r.bottom();
  int32_t right = (int32_t)r.right();
  for (int32_t i = (int32_t)r.top; i < bottom; i++) {
    FX_ARGB* pSrcLine = pSrcBuf + (int32_t)r.left;
    FX_ARGB* pDstLine = pDstBuf + (int32_t)r.left;
    for (int32_t j = (int32_t)r.left; j < right; j++) {
      FX_ARGB c = *pDstLine;
      *pDstLine++ =
          ArgbEncode(FXARGB_A(c), ~((c & 0xFFFFFF) ^ (*pSrcLine & 0xFFFFFF)));
      pSrcLine++;
    }
    pSrcBuf = (FX_ARGB*)((uint8_t*)pSrcBuf + srcBitmap->GetPitch());
    pDstBuf = (FX_ARGB*)((uint8_t*)pDstBuf + dst->GetPitch());
  }
  return FX_ERR_Succeeded;
}
FX_ERR CFX_Graphics::RenderDeviceSetLineDash(FX_DashStyle dashStyle) {
  switch (dashStyle) {
    case FX_DASHSTYLE_Solid: {
      _info._graphState.SetDashCount(0);
      return FX_ERR_Succeeded;
    }
    case FX_DASHSTYLE_Dash: {
      FX_FLOAT dashArray[] = {3, 1};
      SetLineDash(0, dashArray, 2);
      return FX_ERR_Succeeded;
    }
    case FX_DASHSTYLE_Dot: {
      FX_FLOAT dashArray[] = {1, 1};
      SetLineDash(0, dashArray, 2);
      return FX_ERR_Succeeded;
    }
    case FX_DASHSTYLE_DashDot: {
      FX_FLOAT dashArray[] = {3, 1, 1, 1};
      SetLineDash(0, dashArray, 4);
      return FX_ERR_Succeeded;
    }
    case FX_DASHSTYLE_DashDotDot: {
      FX_FLOAT dashArray[] = {4, 1, 2, 1, 2, 1};
      SetLineDash(0, dashArray, 6);
      return FX_ERR_Succeeded;
    }
    default: { return FX_ERR_Parameter_Invalid; }
  }
}
FX_ERR CFX_Graphics::RenderDeviceStrokePath(CFX_Path* path,
                                            CFX_Matrix* matrix) {
  if (!_info._strokeColor)
    return FX_ERR_Property_Invalid;
  CFX_Matrix m;
  m.Set(_info._CTM.a, _info._CTM.b, _info._CTM.c, _info._CTM.d, _info._CTM.e,
        _info._CTM.f);
  if (matrix) {
    m.Concat(*matrix);
  }
  switch (_info._strokeColor->_type) {
    case FX_COLOR_Solid: {
      FX_BOOL result = _renderDevice->DrawPath(
          path->GetPathData(), (CFX_Matrix*)&m, &_info._graphState, 0x0,
          _info._strokeColor->_argb, 0);
      if (!result)
        return FX_ERR_Indefinite;
      return FX_ERR_Succeeded;
    }
    case FX_COLOR_Pattern: {
      return StrokePathWithPattern(path, &m);
    }
    case FX_COLOR_Shading: {
      return StrokePathWithShading(path, &m);
    }
    default: { return FX_ERR_Property_Invalid; }
  }
}
FX_ERR CFX_Graphics::RenderDeviceFillPath(CFX_Path* path,
                                          FX_FillMode fillMode,
                                          CFX_Matrix* matrix) {
  if (!_info._fillColor)
    return FX_ERR_Property_Invalid;
  CFX_Matrix m;
  m.Set(_info._CTM.a, _info._CTM.b, _info._CTM.c, _info._CTM.d, _info._CTM.e,
        _info._CTM.f);
  if (matrix) {
    m.Concat(*matrix);
  }
  switch (_info._fillColor->_type) {
    case FX_COLOR_Solid: {
      FX_BOOL result = _renderDevice->DrawPath(
          path->GetPathData(), (CFX_Matrix*)&m, &_info._graphState,
          _info._fillColor->_argb, 0x0, fillMode);
      if (!result)
        return FX_ERR_Indefinite;
      return FX_ERR_Succeeded;
    }
    case FX_COLOR_Pattern: {
      { return FillPathWithPattern(path, fillMode, &m); }
    }
    case FX_COLOR_Shading: {
      { return FillPathWithShading(path, fillMode, &m); }
    }
    default: { return FX_ERR_Property_Invalid; }
  }
}
FX_ERR CFX_Graphics::RenderDeviceDrawImage(CFX_DIBSource* source,
                                           const CFX_PointF& point,
                                           CFX_Matrix* matrix) {
  CFX_Matrix m1;
  m1.Set(_info._CTM.a, _info._CTM.b, _info._CTM.c, _info._CTM.d, _info._CTM.e,
         _info._CTM.f);
  if (matrix) {
    m1.Concat(*matrix);
  }
  CFX_Matrix m2;
  m2.Set((FX_FLOAT)source->GetWidth(), 0.0, 0.0, (FX_FLOAT)source->GetHeight(),
         point.x, point.y);
  m2.Concat(m1);
  int32_t left, top;
  CFX_DIBitmap* bmp1 = source->FlipImage(FALSE, TRUE);
  CFX_DIBitmap* bmp2 = bmp1->TransformTo((CFX_Matrix*)&m2, left, top);
  CFX_RectF r;
  GetClipRect(r);
  FX_ERR result = FX_ERR_Indefinite;
  {
    CFX_DIBitmap* bitmap = _renderDevice->GetBitmap();
    CFX_DIBitmap bmp;
    bmp.Create(bitmap->GetWidth(), bitmap->GetHeight(), FXDIB_Argb);
    _renderDevice->GetDIBits(&bmp, 0, 0);
    bmp.TransferBitmap(FXSYS_round(r.left), FXSYS_round(r.top),
                       FXSYS_round(r.Width()), FXSYS_round(r.Height()), bmp2,
                       FXSYS_round(r.left - left), FXSYS_round(r.top - top));
    _renderDevice->SetDIBits(&bmp, 0, 0);
    result = FX_ERR_Succeeded;
  }
  if (bmp2) {
    delete bmp2;
    bmp2 = NULL;
  }
  if (bmp1) {
    delete bmp1;
    bmp1 = NULL;
  }
  return result;
}
FX_ERR CFX_Graphics::RenderDeviceStretchImage(CFX_DIBSource* source,
                                              const CFX_RectF& rect,
                                              CFX_Matrix* matrix) {
  CFX_Matrix m1;
  m1.Set(_info._CTM.a, _info._CTM.b, _info._CTM.c, _info._CTM.d, _info._CTM.e,
         _info._CTM.f);
  if (matrix) {
    m1.Concat(*matrix);
  }
  CFX_DIBitmap* bmp1 =
      source->StretchTo((int32_t)rect.Width(), (int32_t)rect.Height());
  CFX_Matrix m2;
  m2.Set(rect.Width(), 0.0, 0.0, rect.Height(), rect.left, rect.top);
  m2.Concat(m1);
  int32_t left, top;
  CFX_DIBitmap* bmp2 = bmp1->FlipImage(FALSE, TRUE);
  CFX_DIBitmap* bmp3 = bmp2->TransformTo((CFX_Matrix*)&m2, left, top);
  CFX_RectF r;
  GetClipRect(r);
  FX_ERR result = FX_ERR_Indefinite;
  {
    CFX_DIBitmap* bitmap = _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));
    result = FX_ERR_Succeeded;
  }
  if (bmp3) {
    delete bmp3;
    bmp3 = NULL;
  }
  if (bmp2) {
    delete bmp2;
    bmp2 = NULL;
  }
  if (bmp1) {
    delete bmp1;
    bmp1 = NULL;
  }
  return result;
}
FX_ERR CFX_Graphics::RenderDeviceShowText(const CFX_PointF& point,
                                          const CFX_WideString& text,
                                          CFX_Matrix* matrix) {
  int32_t length = text.GetLength();
  FX_DWORD* charCodes = FX_Alloc(FX_DWORD, length);
  FXTEXT_CHARPOS* charPos = FX_Alloc(FXTEXT_CHARPOS, length);
  CFX_RectF rect;
  rect.Set(point.x, point.y, 0, 0);
  CalcTextInfo(text, charCodes, charPos, rect);
  CFX_Matrix m;
  m.Set(_info._CTM.a, _info._CTM.b, _info._CTM.c, _info._CTM.d, _info._CTM.e,
        _info._CTM.f);
  m.Translate(0, _info._fontSize * _info._fontHScale);
  if (matrix) {
    m.Concat(*matrix);
  }
  FX_BOOL result = _renderDevice->DrawNormalText(
      length, charPos, _info._font, CFX_GEModule::Get()->GetFontCache(),
      -_info._fontSize * _info._fontHScale, (CFX_Matrix*)&m,
      _info._fillColor->_argb, FXTEXT_CLEARTYPE);
  if (!result)
    return FX_ERR_Indefinite;
  FX_Free(charPos);
  FX_Free(charCodes);
  return FX_ERR_Succeeded;
}
FX_ERR CFX_Graphics::StrokePathWithPattern(CFX_Path* path, CFX_Matrix* matrix) {
  return FX_ERR_Method_Not_Supported;
}
FX_ERR CFX_Graphics::StrokePathWithShading(CFX_Path* path, CFX_Matrix* matrix) {
  return FX_ERR_Method_Not_Supported;
}
FX_ERR CFX_Graphics::FillPathWithPattern(CFX_Path* path,
                                         FX_FillMode fillMode,
                                         CFX_Matrix* matrix) {
  CFX_Pattern* pattern = _info._fillColor->_pattern;
  CFX_DIBitmap* bitmap = _renderDevice->GetBitmap();
  int32_t width = bitmap->GetWidth();
  int32_t height = bitmap->GetHeight();
  CFX_DIBitmap bmp;
  bmp.Create(width, height, FXDIB_Argb);
  _renderDevice->GetDIBits(&bmp, 0, 0);
  switch (pattern->_type) {
    case FX_PATTERN_Bitmap: {
      int32_t xStep = FXSYS_round(pattern->_x1Step);
      int32_t yStep = FXSYS_round(pattern->_y1Step);
      int32_t xCount = width / xStep + 1;
      int32_t yCount = height / yStep + 1;
      for (int32_t i = 0; i <= yCount; i++) {
        for (int32_t j = 0; j <= xCount; j++) {
          bmp.TransferBitmap(j * xStep, i * yStep, xStep, yStep,
                             pattern->_bitmap, 0, 0);
        }
      }
      break;
    }
    case FX_PATTERN_Hatch: {
      FX_HatchStyle hatchStyle = _info._fillColor->_pattern->_hatchStyle;
      if (hatchStyle < FX_HATCHSTYLE_Horizontal ||
          hatchStyle > FX_HATCHSTYLE_SolidDiamond) {
        return FX_ERR_Intermediate_Value_Invalid;
      }
      const FX_HATCHDATA& data = hatchBitmapData[hatchStyle];
      CFX_DIBitmap mask;
      mask.Create(data.width, data.height, FXDIB_1bppMask);
      FXSYS_memcpy(mask.GetBuffer(), data.maskBits,
                   mask.GetPitch() * data.height);
      CFX_FloatRect rectf = path->GetPathData()->GetBoundingBox();
      if (matrix) {
        rectf.Transform((const CFX_Matrix*)matrix);
      }
      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);
      device.FillRect(&rect, _info._fillColor->_pattern->_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, _info._fillColor->_pattern->_foreArgb);
        }
      }
      break;
    }
  }
  _renderDevice->SaveState();
  _renderDevice->SetClip_PathFill(path->GetPathData(), (CFX_Matrix*)matrix,
                                  fillMode);
  SetDIBitsWithMatrix(&bmp, &pattern->_matrix);
  _renderDevice->RestoreState();
  return FX_ERR_Succeeded;
}
FX_ERR CFX_Graphics::FillPathWithShading(CFX_Path* path,
                                         FX_FillMode fillMode,
                                         CFX_Matrix* matrix) {
  CFX_DIBitmap* bitmap = _renderDevice->GetBitmap();
  int32_t width = bitmap->GetWidth();
  int32_t height = bitmap->GetHeight();
  FX_FLOAT start_x = _info._fillColor->_shading->_beginPoint.x;
  FX_FLOAT start_y = _info._fillColor->_shading->_beginPoint.y;
  FX_FLOAT end_x = _info._fillColor->_shading->_endPoint.x;
  FX_FLOAT end_y = _info._fillColor->_shading->_endPoint.y;
  CFX_DIBitmap bmp;
  bmp.Create(width, height, FXDIB_Argb);
  _renderDevice->GetDIBits(&bmp, 0, 0);
  int32_t pitch = bmp.GetPitch();
  FX_BOOL result = FALSE;
  switch (_info._fillColor->_shading->_type) {
    case FX_SHADING_Axial: {
      FX_FLOAT x_span = end_x - start_x;
      FX_FLOAT y_span = end_y - start_y;
      FX_FLOAT axis_len_square = (x_span * x_span) + (y_span * y_span);
      for (int32_t row = 0; row < height; row++) {
        FX_DWORD* dib_buf = (FX_DWORD*)(bmp.GetBuffer() + row * pitch);
        for (int32_t column = 0; column < width; column++) {
          FX_FLOAT x = (FX_FLOAT)(column);
          FX_FLOAT y = (FX_FLOAT)(row);
          FX_FLOAT scale =
              (((x - start_x) * x_span) + ((y - start_y) * y_span)) /
              axis_len_square;
          if (scale < 0) {
            if (!_info._fillColor->_shading->_isExtendedBegin) {
              continue;
            }
            scale = 0;
          } else if (scale > 1.0f) {
            if (!_info._fillColor->_shading->_isExtendedEnd) {
              continue;
            }
            scale = 1.0f;
          }
          int32_t index = (int32_t)(scale * (FX_SHADING_Steps - 1));
          dib_buf[column] = _info._fillColor->_shading->_argbArray[index];
        }
      }
      result = TRUE;
      break;
    }
    case FX_SHADING_Radial: {
      FX_FLOAT start_r = _info._fillColor->_shading->_beginRadius;
      FX_FLOAT end_r = _info._fillColor->_shading->_endRadius;
      FX_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++) {
        FX_DWORD* dib_buf = (FX_DWORD*)(bmp.GetBuffer() + row * pitch);
        for (int32_t column = 0; column < width; column++) {
          FX_FLOAT x = (FX_FLOAT)(column);
          FX_FLOAT y = (FX_FLOAT)(row);
          FX_FLOAT b = -2 * (((x - start_x) * (end_x - start_x)) +
                             ((y - start_y) * (end_y - start_y)) +
                             (start_r * (end_r - start_r)));
          FX_FLOAT c = ((x - start_x) * (x - start_x)) +
                       ((y - start_y) * (y - start_y)) - (start_r * start_r);
          FX_FLOAT s;
          if (a == 0) {
            s = -c / b;
          } else {
            FX_FLOAT b2_4ac = (b * b) - 4 * (a * c);
            if (b2_4ac < 0) {
              continue;
            }
            FX_FLOAT root = (FXSYS_sqrt(b2_4ac));
            FX_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 || _info._fillColor->_shading->_isExtendedEnd) {
              s = (s2);
            } else {
              s = (s1);
            }
            if ((start_r) + s * (end_r - start_r) < 0) {
              continue;
            }
          }
          if (s < 0) {
            if (!_info._fillColor->_shading->_isExtendedBegin) {
              continue;
            }
            s = 0;
          }
          if (s > 1.0f) {
            if (!_info._fillColor->_shading->_isExtendedEnd) {
              continue;
            }
            s = 1.0f;
          }
          int index = (int32_t)(s * (FX_SHADING_Steps - 1));
          dib_buf[column] = _info._fillColor->_shading->_argbArray[index];
        }
      }
      result = TRUE;
      break;
    }
    default: { result = FALSE; }
  }
  if (result) {
    _renderDevice->SaveState();
    _renderDevice->SetClip_PathFill(path->GetPathData(), (CFX_Matrix*)matrix,
                                    fillMode);
    SetDIBitsWithMatrix(&bmp, matrix);
    _renderDevice->RestoreState();
  }
  return result;
}
FX_ERR CFX_Graphics::SetDIBitsWithMatrix(CFX_DIBSource* source,
                                         CFX_Matrix* matrix) {
  if (matrix->IsIdentity()) {
    _renderDevice->SetDIBits(source, 0, 0);
  } else {
    CFX_Matrix m;
    m.Set((FX_FLOAT)source->GetWidth(), 0, 0, (FX_FLOAT)source->GetHeight(), 0,
          0);
    m.Concat(*matrix);
    int32_t left, top;
    CFX_DIBitmap* bmp1 = source->FlipImage(FALSE, TRUE);
    CFX_DIBitmap* bmp2 = bmp1->TransformTo((CFX_Matrix*)&m, left, top);
    _renderDevice->SetDIBits(bmp2, left, top);
    if (bmp2) {
      delete bmp2;
      bmp2 = NULL;
    }
    if (bmp1) {
      delete bmp1;
      bmp1 = NULL;
    }
  }
  return FX_ERR_Succeeded;
}
FX_ERR CFX_Graphics::CalcTextInfo(const CFX_WideString& text,
                                  FX_DWORD* charCodes,
                                  FXTEXT_CHARPOS* charPos,
                                  CFX_RectF& rect) {
  std::unique_ptr<CFX_UnicodeEncoding> encoding(
      new CFX_UnicodeEncoding(_info._font));
  int32_t length = text.GetLength();
  FX_FLOAT penX = (FX_FLOAT)rect.left;
  FX_FLOAT penY = (FX_FLOAT)rect.top;
  FX_FLOAT left = (FX_FLOAT)(0);
  FX_FLOAT top = (FX_FLOAT)(0);
  charCodes[0] = text.GetAt(0);
  charPos[0].m_OriginX = penX + left;
  charPos[0].m_OriginY = penY + top;
  charPos[0].m_GlyphIndex = encoding->GlyphFromCharCode(charCodes[0]);
  charPos[0].m_FontCharWidth = FXSYS_round(
      _info._font->GetGlyphWidth(charPos[0].m_GlyphIndex) * _info._fontHScale);
  charPos[0].m_bGlyphAdjust = TRUE;
  charPos[0].m_AdjustMatrix[0] = -1;
  charPos[0].m_AdjustMatrix[1] = 0;
  charPos[0].m_AdjustMatrix[2] = 0;
  charPos[0].m_AdjustMatrix[3] = 1;
  penX += (FX_FLOAT)(charPos[0].m_FontCharWidth) * _info._fontSize / 1000 +
          _info._fontSpacing;
  for (int32_t i = 1; i < length; i++) {
    charCodes[i] = text.GetAt(i);
    charPos[i].m_OriginX = penX + left;
    charPos[i].m_OriginY = penY + top;
    charPos[i].m_GlyphIndex = encoding->GlyphFromCharCode(charCodes[i]);
    charPos[i].m_FontCharWidth =
        FXSYS_round(_info._font->GetGlyphWidth(charPos[i].m_GlyphIndex) *
                    _info._fontHScale);
    charPos[i].m_bGlyphAdjust = TRUE;
    charPos[i].m_AdjustMatrix[0] = -1;
    charPos[i].m_AdjustMatrix[1] = 0;
    charPos[i].m_AdjustMatrix[2] = 0;
    charPos[i].m_AdjustMatrix[3] = 1;
    penX += (FX_FLOAT)(charPos[i].m_FontCharWidth) * _info._fontSize / 1000 +
            _info._fontSpacing;
  }
  rect.width = (FX_FLOAT)penX - rect.left;
  rect.height = rect.top + _info._fontSize * _info._fontHScale - rect.top;
  return FX_ERR_Succeeded;
}
CAGG_Graphics::CAGG_Graphics() {
  _owner = NULL;
}
FX_ERR CAGG_Graphics::Create(CFX_Graphics* owner,
                             int32_t width,
                             int32_t height,
                             FXDIB_Format format) {
  if (owner->_renderDevice) {
    return FX_ERR_Parameter_Invalid;
  }
  if (_owner) {
    return FX_ERR_Property_Invalid;
  }
  CFX_FxgeDevice* device = new CFX_FxgeDevice;
  device->Create(width, height, format);
  _owner = owner;
  _owner->_renderDevice = device;
  _owner->_renderDevice->GetBitmap()->Clear(0xFFFFFFFF);
  return FX_ERR_Succeeded;
}
CAGG_Graphics::~CAGG_Graphics() {
  if (_owner->_renderDevice) {
    delete (CFX_FxgeDevice*)_owner->_renderDevice;
  }
  _owner = NULL;
}
CFX_Path::CFX_Path() {
  _generator = NULL;
}
FX_ERR CFX_Path::Create() {
  if (_generator) {
    return FX_ERR_Property_Invalid;
  }
  _generator = new CFX_PathGenerator;
  _generator->Create();
  return FX_ERR_Succeeded;
}
CFX_Path::~CFX_Path() {
  if (_generator) {
    delete _generator;
    _generator = NULL;
  }
}
FX_ERR CFX_Path::MoveTo(FX_FLOAT x, FX_FLOAT y) {
  if (!_generator)
    return FX_ERR_Property_Invalid;
  _generator->MoveTo(x, y);
  return FX_ERR_Succeeded;
}
FX_ERR CFX_Path::LineTo(FX_FLOAT x, FX_FLOAT y) {
  if (!_generator)
    return FX_ERR_Property_Invalid;
  _generator->LineTo(x, y);
  return FX_ERR_Succeeded;
}
FX_ERR CFX_Path::BezierTo(FX_FLOAT ctrlX1,
                          FX_FLOAT ctrlY1,
                          FX_FLOAT ctrlX2,
                          FX_FLOAT ctrlY2,
                          FX_FLOAT toX,
                          FX_FLOAT toY) {
  if (!_generator)
    return FX_ERR_Property_Invalid;
  _generator->BezierTo(ctrlX1, ctrlY1, ctrlX2, ctrlY2, toX, toY);
  return FX_ERR_Succeeded;
}
FX_ERR CFX_Path::ArcTo(FX_FLOAT left,
                       FX_FLOAT top,
                       FX_FLOAT width,
                       FX_FLOAT height,
                       FX_FLOAT startAngle,
                       FX_FLOAT sweepAngle) {
  if (!_generator)
    return FX_ERR_Property_Invalid;
  _generator->ArcTo(left + width / 2, top + height / 2, width / 2, height / 2,
                    startAngle, sweepAngle);
  return FX_ERR_Succeeded;
}
FX_ERR CFX_Path::Close() {
  if (!_generator)
    return FX_ERR_Property_Invalid;
  _generator->Close();
  return FX_ERR_Succeeded;
}
FX_ERR CFX_Path::AddLine(FX_FLOAT x1, FX_FLOAT y1, FX_FLOAT x2, FX_FLOAT y2) {
  if (!_generator)
    return FX_ERR_Property_Invalid;
  _generator->AddLine(x1, y1, x2, y2);
  return FX_ERR_Succeeded;
}
FX_ERR CFX_Path::AddBezier(FX_FLOAT startX,
                           FX_FLOAT startY,
                           FX_FLOAT ctrlX1,
                           FX_FLOAT ctrlY1,
                           FX_FLOAT ctrlX2,
                           FX_FLOAT ctrlY2,
                           FX_FLOAT endX,
                           FX_FLOAT endY) {
  if (!_generator)
    return FX_ERR_Property_Invalid;
  _generator->AddBezier(startX, startY, ctrlX1, ctrlY1, ctrlX2, ctrlY2, endX,
                        endY);
  return FX_ERR_Succeeded;
}
FX_ERR CFX_Path::AddRectangle(FX_FLOAT left,
                              FX_FLOAT top,
                              FX_FLOAT width,
                              FX_FLOAT height) {
  if (!_generator)
    return FX_ERR_Property_Invalid;
  _generator->AddRectangle(left, top, left + width, top + height);
  return FX_ERR_Succeeded;
}
FX_ERR CFX_Path::AddEllipse(FX_FLOAT left,
                            FX_FLOAT top,
                            FX_FLOAT width,
                            FX_FLOAT height) {
  if (!_generator)
    return FX_ERR_Property_Invalid;
  _generator->AddEllipse(left + width / 2, top + height / 2, width / 2,
                         height / 2);
  return FX_ERR_Succeeded;
}
FX_ERR CFX_Path::AddEllipse(const CFX_RectF& rect) {
  if (!_generator)
    return FX_ERR_Property_Invalid;
  _generator->AddEllipse(rect.left + rect.Width() / 2,
                         rect.top + rect.Height() / 2, rect.Width() / 2,
                         rect.Height() / 2);
  return FX_ERR_Succeeded;
}
FX_ERR CFX_Path::AddArc(FX_FLOAT left,
                        FX_FLOAT top,
                        FX_FLOAT width,
                        FX_FLOAT height,
                        FX_FLOAT startAngle,
                        FX_FLOAT sweepAngle) {
  if (!_generator)
    return FX_ERR_Property_Invalid;
  _generator->AddArc(left + width / 2, top + height / 2, width / 2, height / 2,
                     startAngle, sweepAngle);
  return FX_ERR_Succeeded;
}
FX_ERR CFX_Path::AddPie(FX_FLOAT left,
                        FX_FLOAT top,
                        FX_FLOAT width,
                        FX_FLOAT height,
                        FX_FLOAT startAngle,
                        FX_FLOAT sweepAngle) {
  if (!_generator)
    return FX_ERR_Property_Invalid;
  _generator->AddPie(left + width / 2, top + height / 2, width / 2, height / 2,
                     startAngle, sweepAngle);
  return FX_ERR_Succeeded;
}
FX_ERR CFX_Path::AddSubpath(CFX_Path* path) {
  if (!_generator)
    return FX_ERR_Property_Invalid;
  _generator->AddPathData(path->GetPathData());
  return FX_ERR_Succeeded;
}
FX_ERR CFX_Path::Clear() {
  if (!_generator)
    return FX_ERR_Property_Invalid;
  _generator->GetPathData()->SetPointCount(0);
  return FX_ERR_Succeeded;
}
FX_BOOL CFX_Path::IsEmpty() {
  if (!_generator)
    return FX_ERR_Property_Invalid;
  if (_generator->GetPathData()->GetPointCount() == 0) {
    return TRUE;
  }
  return FALSE;
}
CFX_PathData* CFX_Path::GetPathData() {
  if (!_generator)
    return NULL;
  return _generator->GetPathData();
}
CFX_Color::CFX_Color() {
  _type = FX_COLOR_None;
}
CFX_Color::CFX_Color(const FX_ARGB argb) {
  _type = FX_COLOR_None;
  Set(argb);
}
CFX_Color::CFX_Color(CFX_Pattern* pattern, const FX_ARGB argb) {
  _type = FX_COLOR_None;
  Set(pattern, argb);
}
CFX_Color::CFX_Color(CFX_Shading* shading) {
  _type = FX_COLOR_None;
  Set(shading);
}
CFX_Color::~CFX_Color() {
  _type = FX_COLOR_None;
}
FX_ERR CFX_Color::Set(const FX_ARGB argb) {
  _type = FX_COLOR_Solid;
  _argb = argb;
  _pattern = NULL;
  return FX_ERR_Succeeded;
}
FX_ERR CFX_Color::Set(CFX_Pattern* pattern, const FX_ARGB argb) {
  if (!pattern)
    return FX_ERR_Parameter_Invalid;
  _type = FX_COLOR_Pattern;
  _argb = argb;
  _pattern = pattern;
  return FX_ERR_Succeeded;
}
FX_ERR CFX_Color::Set(CFX_Shading* shading) {
  if (!shading)
    return FX_ERR_Parameter_Invalid;
  _type = FX_COLOR_Shading;
  _shading = shading;
  return FX_ERR_Succeeded;
}
CFX_Pattern::CFX_Pattern() {
  _type = FX_PATTERN_None;
  _matrix.SetIdentity();
}
FX_ERR CFX_Pattern::Create(CFX_DIBitmap* bitmap,
                           const FX_FLOAT xStep,
                           const FX_FLOAT yStep,
                           CFX_Matrix* matrix) {
  if (!bitmap)
    return FX_ERR_Parameter_Invalid;
  if (_type != FX_PATTERN_None) {
    return FX_ERR_Property_Invalid;
  }
  _type = FX_PATTERN_Bitmap;
  _bitmap = bitmap;
  _x1Step = xStep;
  _y1Step = yStep;
  if (matrix) {
    _matrix.Set(matrix->a, matrix->b, matrix->c, matrix->d, matrix->e,
                matrix->f);
  }
  return FX_ERR_Succeeded;
}
FX_ERR CFX_Pattern::Create(FX_HatchStyle hatchStyle,
                           const FX_ARGB foreArgb,
                           const FX_ARGB backArgb,
                           CFX_Matrix* matrix) {
  if (hatchStyle < FX_HATCHSTYLE_Horizontal ||
      hatchStyle > FX_HATCHSTYLE_SolidDiamond) {
    return FX_ERR_Parameter_Invalid;
  }
  if (_type != FX_PATTERN_None) {
    return FX_ERR_Property_Invalid;
  }
  _type = FX_PATTERN_Hatch;
  _hatchStyle = hatchStyle;
  _foreArgb = foreArgb;
  _backArgb = backArgb;
  if (matrix) {
    _matrix.Set(matrix->a, matrix->b, matrix->c, matrix->d, matrix->e,
                matrix->f);
  }
  return FX_ERR_Succeeded;
}
CFX_Pattern::~CFX_Pattern() {
  _type = FX_PATTERN_None;
}
CFX_Shading::CFX_Shading() {
  _type = FX_SHADING_None;
}
FX_ERR CFX_Shading::CreateAxial(const CFX_PointF& beginPoint,
                                const CFX_PointF& endPoint,
                                FX_BOOL isExtendedBegin,
                                FX_BOOL isExtendedEnd,
                                const FX_ARGB beginArgb,
                                const FX_ARGB endArgb) {
  if (_type != FX_SHADING_None) {
    return FX_ERR_Property_Invalid;
  }
  _type = FX_SHADING_Axial;
  _beginPoint = beginPoint;
  _endPoint = endPoint;
  _isExtendedBegin = isExtendedBegin;
  _isExtendedEnd = isExtendedEnd;
  _beginArgb = beginArgb;
  _endArgb = endArgb;
  return InitArgbArray();
}
FX_ERR CFX_Shading::CreateRadial(const CFX_PointF& beginPoint,
                                 const CFX_PointF& endPoint,
                                 const FX_FLOAT beginRadius,
                                 const FX_FLOAT endRadius,
                                 FX_BOOL isExtendedBegin,
                                 FX_BOOL isExtendedEnd,
                                 const FX_ARGB beginArgb,
                                 const FX_ARGB endArgb) {
  if (_type != FX_SHADING_None) {
    return FX_ERR_Property_Invalid;
  }
  _type = FX_SHADING_Radial;
  _beginPoint = beginPoint;
  _endPoint = endPoint;
  _beginRadius = beginRadius;
  _endRadius = endRadius;
  _isExtendedBegin = isExtendedBegin;
  _isExtendedEnd = isExtendedEnd;
  _beginArgb = beginArgb;
  _endArgb = endArgb;
  return InitArgbArray();
}
CFX_Shading::~CFX_Shading() {
  _type = FX_SHADING_None;
}
FX_ERR CFX_Shading::InitArgbArray() {
  int32_t a1, r1, g1, b1;
  ArgbDecode(_beginArgb, a1, r1, g1, b1);
  int32_t a2, r2, g2, b2;
  ArgbDecode(_endArgb, a2, r2, g2, b2);
  FX_FLOAT f = (FX_FLOAT)(FX_SHADING_Steps - 1);
  FX_FLOAT aScale = (FX_FLOAT)(1.0 * (a2 - a1) / f);
  FX_FLOAT rScale = (FX_FLOAT)(1.0 * (r2 - r1) / f);
  FX_FLOAT gScale = (FX_FLOAT)(1.0 * (g2 - g1) / f);
  FX_FLOAT bScale = (FX_FLOAT)(1.0 * (b2 - b1) / f);
  int32_t a3, r3, g3, b3;
  for (int32_t i = 0; i < FX_SHADING_Steps; i++) {
    a3 = (int32_t)(i * aScale);
    r3 = (int32_t)(i * rScale);
    g3 = (int32_t)(i * gScale);
    b3 = (int32_t)(i * bScale);
    _argbArray[i] =
        FXARGB_TODIB(FXARGB_MAKE((a1 + a3), (r1 + r3), (g1 + g3), (b1 + b3)));
  }
  return FX_ERR_Succeeded;
}
class CFX_Pause : public IFX_Pause {
 public:
  virtual FX_BOOL NeedToPauseNow() { return TRUE; }
};