// 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

#ifndef XFA_FXFA_APP_XFA_TEXTLAYOUT_H_
#define XFA_FXFA_APP_XFA_TEXTLAYOUT_H_

#include "xfa/fde/css/fde_css.h"
#include "xfa/fde/fde_brush.h"
#include "xfa/fde/fde_renderdevice.h"
#include "xfa/fgas/layout/fgas_rtfbreak.h"
#include "xfa/fxfa/app/xfa_ffdoc.h"
#include "xfa/fxfa/parser/xfa_object.h"

#define XFA_LOADERCNTXTFLG_FILTERSPACE 0x001

class CXFA_Para;
class CXFA_Font;
class CXFA_TextTabstopsContext;

class IXFA_TextProvider {
 public:
  virtual ~IXFA_TextProvider() {}
  virtual CXFA_Node* GetTextNode(FX_BOOL& bRichText) = 0;
  virtual CXFA_Para GetParaNode() = 0;
  virtual CXFA_Font GetFontNode() = 0;
  virtual FX_BOOL IsCheckButtonAndAutoWidth() = 0;
  virtual CXFA_FFDoc* GetDocNode() = 0;
  virtual FX_BOOL GetEmbbedObj(FX_BOOL bURI,
                               FX_BOOL bRaw,
                               const CFX_WideString& wsAttr,
                               CFX_WideString& wsValue) = 0;
};

class CXFA_CSSTagProvider : public IFDE_CSSTagProvider {
 public:
  CXFA_CSSTagProvider() : m_bTagAviliable(FALSE), m_bContent(FALSE) {}
  virtual ~CXFA_CSSTagProvider();
  virtual CFX_WideStringC GetTagName() { return m_wsTagName; }
  virtual FX_POSITION GetFirstAttribute() {
    return m_Attributes.GetStartPosition();
  }
  virtual void GetNextAttribute(FX_POSITION& pos,
                                CFX_WideStringC& wsAttr,
                                CFX_WideStringC& wsValue);
  void SetTagNameObj(const CFX_WideString& wsName) { m_wsTagName = wsName; }
  void SetAttribute(const CFX_WideString& wsAttr,
                    const CFX_WideString& wsValue);
  FX_BOOL m_bTagAviliable;
  FX_BOOL m_bContent;

 protected:
  CFX_WideString m_wsTagName;
  CFX_MapPtrToPtr m_Attributes;
};

class CXFA_TextParseContext : public CFX_Target {
 public:
  CXFA_TextParseContext()
      : m_pParentStyle(nullptr),
        m_ppMatchedDecls(nullptr),
        m_dwMatchedDecls(0),
        m_eDisplay(FDE_CSSDISPLAY_None) {}
  ~CXFA_TextParseContext() {
    if (m_pParentStyle)
      m_pParentStyle->Release();
    FX_Free(m_ppMatchedDecls);
  }
  void SetDisplay(FDE_CSSDISPLAY eDisplay) { m_eDisplay = eDisplay; }
  FDE_CSSDISPLAY GetDisplay() const { return m_eDisplay; }
  void SetDecls(const IFDE_CSSDeclaration** ppDeclArray, int32_t iDeclCount);
  const IFDE_CSSDeclaration** GetDecls() {
    return (const IFDE_CSSDeclaration**)m_ppMatchedDecls;
  }
  FX_DWORD CountDecls() const { return m_dwMatchedDecls; }
  IFDE_CSSComputedStyle* m_pParentStyle;

 protected:
  IFDE_CSSDeclaration** m_ppMatchedDecls;
  FX_DWORD m_dwMatchedDecls;
  FDE_CSSDISPLAY m_eDisplay;
};

class CXFA_TextParser {
 public:
  CXFA_TextParser() : m_pAllocator(NULL), m_pSelector(NULL), m_pUASheet(NULL) {}
  virtual ~CXFA_TextParser();
  void Reset();
  void DoParse(IFDE_XMLNode* pXMLContainer, IXFA_TextProvider* pTextProvider);
  IFDE_CSSComputedStyle* CreateRootStyle(IXFA_TextProvider* pTextProvider);
  IFDE_CSSComputedStyle* ComputeStyle(IFDE_XMLNode* pXMLNode,
                                      IFDE_CSSComputedStyle* pParentStyle);
  FX_BOOL IsParsed() const { return m_pAllocator != NULL; }

  int32_t GetVAlgin(IXFA_TextProvider* pTextProvider) const;
  FX_FLOAT GetTabInterval(IFDE_CSSComputedStyle* pStyle) const;
  int32_t CountTabs(IFDE_CSSComputedStyle* pStyle) const;
  FX_BOOL IsSpaceRun(IFDE_CSSComputedStyle* pStyle) const;
  FX_BOOL GetTabstops(IFDE_CSSComputedStyle* pStyle,
                      CXFA_TextTabstopsContext* pTabstopContext);
  IFX_Font* GetFont(IXFA_TextProvider* pTextProvider,
                    IFDE_CSSComputedStyle* pStyle) const;
  FX_FLOAT GetFontSize(IXFA_TextProvider* pTextProvider,
                       IFDE_CSSComputedStyle* pStyle) const;
  int32_t GetHorScale(IXFA_TextProvider* pTextProvider,
                      IFDE_CSSComputedStyle* pStyle,
                      IFDE_XMLNode* pXMLNode) const;
  int32_t GetVerScale(IXFA_TextProvider* pTextProvider,
                      IFDE_CSSComputedStyle* pStyle) const;
  void GetUnderline(IXFA_TextProvider* pTextProvider,
                    IFDE_CSSComputedStyle* pStyle,
                    int32_t& iUnderline,
                    int32_t& iPeriod) const;
  void GetLinethrough(IXFA_TextProvider* pTextProvider,
                      IFDE_CSSComputedStyle* pStyle,
                      int32_t& iLinethrough) const;
  FX_ARGB GetColor(IXFA_TextProvider* pTextProvider,
                   IFDE_CSSComputedStyle* pStyle) const;
  FX_FLOAT GetBaseline(IXFA_TextProvider* pTextProvider,
                       IFDE_CSSComputedStyle* pStyle) const;
  FX_FLOAT GetLineHeight(IXFA_TextProvider* pTextProvider,
                         IFDE_CSSComputedStyle* pStyle,
                         FX_BOOL bFirst,
                         FX_FLOAT fVerScale) const;
  FX_BOOL GetEmbbedObj(IXFA_TextProvider* pTextProvider,
                       IFDE_XMLNode* pXMLNode,
                       CFX_WideString& wsValue);
  CXFA_TextParseContext* GetParseContextFromMap(IFDE_XMLNode* pXMLNode);

 private:
  void InitCSSData(IXFA_TextProvider* pTextProvider);
  void ParseRichText(IFDE_XMLNode* pXMLNode,
                     IFDE_CSSComputedStyle* pParentStyle);
  void ParseTagInfo(IFDE_XMLNode* pXMLNode, CXFA_CSSTagProvider& tagProvider);
  IFDE_CSSStyleSheet* LoadDefaultSheetStyle();
  IFDE_CSSComputedStyle* CreateStyle(IFDE_CSSComputedStyle* pParentStyle);
  IFX_MEMAllocator* m_pAllocator;
  IFDE_CSSStyleSelector* m_pSelector;
  IFDE_CSSStyleSheet* m_pUASheet;
  CFX_MapPtrTemplate<IFDE_XMLNode*, CXFA_TextParseContext*>
      m_mapXMLNodeToParseContext;
};

class CXFA_LoaderContext {
 public:
  CXFA_LoaderContext()
      : m_bSaveLineHeight(FALSE),
        m_fWidth(0),
        m_fHeight(0),
        m_fLastPos(0),
        m_fStartLineOffset(0),
        m_iChar(0),
        m_iTotalLines(-1),
        m_pXMLNode(NULL),
        m_pNode(NULL),
        m_pParentStyle(NULL),
        m_dwFlags(0) {}
  FX_BOOL m_bSaveLineHeight;
  FX_FLOAT m_fWidth;
  FX_FLOAT m_fHeight;
  FX_FLOAT m_fLastPos;
  FX_FLOAT m_fStartLineOffset;
  int32_t m_iChar;
  int32_t m_iLines;
  int32_t m_iTotalLines;
  IFDE_XMLNode* m_pXMLNode;
  CXFA_Node* m_pNode;
  IFDE_CSSComputedStyle* m_pParentStyle;
  CFX_ArrayTemplate<FX_FLOAT> m_lineHeights;
  FX_DWORD m_dwFlags;
  CFX_FloatArray m_BlocksHeight;
};

class CXFA_LinkUserData : public IFX_Unknown, public CFX_Target {
 public:
  CXFA_LinkUserData(IFX_MEMAllocator* pAllocator, FX_WCHAR* pszText)
      : m_pAllocator(pAllocator), m_dwRefCount(1) {
    m_pszURLContent = pszText;
  }
  ~CXFA_LinkUserData() {}
  virtual FX_DWORD Release() {
    FX_DWORD dwRefCount = --m_dwRefCount;
    if (dwRefCount <= 0) {
      FXTARGET_DeleteWith(CXFA_LinkUserData, m_pAllocator, this);
    }
    return dwRefCount;
  }
  virtual FX_DWORD AddRef() { return ++m_dwRefCount; }

 public:
  const FX_WCHAR* GetLinkURL() { return m_pszURLContent; }

 protected:
  IFX_MEMAllocator* m_pAllocator;
  FX_DWORD m_dwRefCount;
  CFX_WideString m_pszURLContent;
};

class CXFA_TextUserData : public IFX_Unknown, public CFX_Target {
 public:
  CXFA_TextUserData(IFX_MEMAllocator* pAllocator, IFDE_CSSComputedStyle* pStyle)
      : m_pStyle(pStyle),
        m_pLinkData(nullptr),
        m_pAllocator(pAllocator),
        m_dwRefCount(0) {
    FXSYS_assert(m_pAllocator);
    if (m_pStyle)
      m_pStyle->AddRef();
  }
  CXFA_TextUserData(IFX_MEMAllocator* pAllocator,
                    IFDE_CSSComputedStyle* pStyle,
                    CXFA_LinkUserData* pLinkData)
      : m_pStyle(pStyle),
        m_pLinkData(pLinkData),
        m_pAllocator(pAllocator),
        m_dwRefCount(0) {
    FXSYS_assert(m_pAllocator);
    if (m_pStyle)
      m_pStyle->AddRef();
  }
  ~CXFA_TextUserData() {
    if (m_pStyle)
      m_pStyle->Release();
    if (m_pLinkData)
      m_pLinkData->Release();
  }
  virtual FX_DWORD Release() {
    FX_DWORD dwRefCount = --m_dwRefCount;
    if (dwRefCount == 0) {
      FXTARGET_DeleteWith(CXFA_TextUserData, m_pAllocator, this);
    }
    return dwRefCount;
  }
  virtual FX_DWORD AddRef() { return ++m_dwRefCount; }

  IFDE_CSSComputedStyle* m_pStyle;
  CXFA_LinkUserData* m_pLinkData;

 protected:
  IFX_MEMAllocator* m_pAllocator;
  FX_DWORD m_dwRefCount;
};

class XFA_TextPiece : public CFX_Target {
 public:
  XFA_TextPiece() : pszText(nullptr), pFont(nullptr), pLinkData(nullptr) {}
  ~XFA_TextPiece() {
    if (pLinkData)
      pLinkData->Release();
  }

  FX_WCHAR* pszText;
  int32_t iChars;
  int32_t* pWidths;
  int32_t iHorScale;
  int32_t iVerScale;
  int32_t iBidiLevel;
  int32_t iUnderline;
  int32_t iPeriod;
  int32_t iLineThrough;
  IFX_Font* pFont;
  FX_ARGB dwColor;
  FX_FLOAT fFontSize;
  CFX_RectF rtPiece;
  CXFA_LinkUserData* pLinkData;
};
typedef CFX_ArrayTemplate<XFA_TextPiece*> CXFA_PieceArray;

class CXFA_PieceLine : public CFX_Target {
 public:
  CXFA_PieceLine() {}
  CXFA_PieceArray m_textPieces;
  CFX_Int32Array m_charCounts;
};
typedef CFX_ArrayTemplate<CXFA_PieceLine*> CXFA_PieceLineArray;

struct XFA_TABSTOPS {
  FX_DWORD dwAlign;
  FX_FLOAT fTabstops;
};

class CXFA_TextTabstopsContext {
 public:
  CXFA_TextTabstopsContext()
      : m_iTabCount(0),
        m_iTabIndex(-1),
        m_bTabstops(FALSE),
        m_fTabWidth(0),
        m_fLeft(0) {}
  void Append(FX_DWORD dwAlign, FX_FLOAT fTabstops) {
    int32_t i = 0;
    for (i = 0; i < m_iTabCount; i++) {
      XFA_TABSTOPS* pTabstop = m_tabstops.GetDataPtr(i);
      if (fTabstops < pTabstop->fTabstops) {
        break;
      }
    }
    m_tabstops.InsertSpaceAt(i, 1);
    XFA_TABSTOPS tabstop;
    tabstop.dwAlign = dwAlign;
    tabstop.fTabstops = fTabstops;
    m_tabstops.SetAt(i, tabstop);
    m_iTabCount++;
  }
  void RemoveAll() {
    m_tabstops.RemoveAll();
    m_iTabCount = 0;
  }
  void Reset() {
    m_iTabIndex = -1;
    m_bTabstops = FALSE;
    m_fTabWidth = 0;
    m_fLeft = 0;
  }
  CFX_ArrayTemplate<XFA_TABSTOPS> m_tabstops;
  int32_t m_iTabCount;
  int32_t m_iTabIndex;
  FX_BOOL m_bTabstops;
  FX_FLOAT m_fTabWidth;
  FX_FLOAT m_fLeft;
};

class CXFA_TextLayout {
 public:
  CXFA_TextLayout(IXFA_TextProvider* pTextProvider);
  virtual ~CXFA_TextLayout();
  int32_t GetText(CFX_WideString& wsText);
  FX_FLOAT GetLayoutHeight();
  FX_FLOAT StartLayout(FX_FLOAT fWidth = -1);
  FX_BOOL DoLayout(int32_t iBlockIndex,
                   FX_FLOAT& fCalcHeight,
                   FX_FLOAT fContentAreaHeight = -1,
                   FX_FLOAT fTextHeight = -1);

  FX_BOOL CalcSize(const CFX_SizeF& minSize,
                   const CFX_SizeF& maxSize,
                   CFX_SizeF& defaultSize);
  FX_BOOL Layout(const CFX_SizeF& size, FX_FLOAT* fHeight = NULL);
  void ItemBlocks(const CFX_RectF& rtText, int32_t iBlockIndex);
  FX_BOOL DrawString(CFX_RenderDevice* pFxDevice,
                     const CFX_Matrix& tmDoc2Device,
                     const CFX_RectF& rtClip,
                     int32_t iBlock = 0);
  FX_BOOL IsLoaded() const { return m_pieceLines.GetSize() > 0; }
  void Unload();
  const CXFA_PieceLineArray* GetPieceLines();

  FX_BOOL m_bHasBlock;
  CFX_Int32Array m_Blocks;

 private:
  void GetTextDataNode();
  IFDE_XMLNode* GetXMLContainerNode();
  IFX_RTFBreak* CreateBreak(FX_BOOL bDefault);
  void InitBreak(FX_FLOAT fLineWidth);
  void InitBreak(IFDE_CSSComputedStyle* pStyle,
                 FDE_CSSDISPLAY eDisplay,
                 FX_FLOAT fLineWidth,
                 IFDE_XMLNode* pXMLNode,
                 IFDE_CSSComputedStyle* pParentStyle = NULL);
  FX_BOOL Loader(const CFX_SizeF& szText,
                 FX_FLOAT& fLinePos,
                 FX_BOOL bSavePieces = TRUE);
  void LoadText(CXFA_Node* pNode,
                const CFX_SizeF& szText,
                FX_FLOAT& fLinePos,
                FX_BOOL bSavePieces);
  FX_BOOL LoadRichText(IFDE_XMLNode* pXMLNode,
                       const CFX_SizeF& szText,
                       FX_FLOAT& fLinePos,
                       IFDE_CSSComputedStyle* pParentStyle,
                       FX_BOOL bSavePieces,
                       CXFA_LinkUserData* pLinkData = NULL,
                       FX_BOOL bEndBreak = TRUE,
                       FX_BOOL bIsOl = FALSE,
                       int32_t iLiCount = 0);
  FX_BOOL AppendChar(const CFX_WideString& wsText,
                     FX_FLOAT& fLinePos,
                     FX_FLOAT fSpaceAbove,
                     FX_BOOL bSavePieces);
  void AppendTextLine(FX_DWORD dwStatus,
                      FX_FLOAT& fLinePos,
                      FX_BOOL bSavePieces,
                      FX_BOOL bEndBreak = FALSE);
  void EndBreak(FX_DWORD dwStatus, FX_FLOAT& fLinePos, FX_BOOL bDefault);
  FX_BOOL IsEnd(FX_BOOL bSavePieces);
  void ProcessText(CFX_WideString& wsText);
  void UpdateAlign(FX_FLOAT fHeight, FX_FLOAT fBottom);
  void RenderString(IFDE_RenderDevice* pDevice,
                    IFDE_SolidBrush* pBrush,
                    CXFA_PieceLine* pPieceLine,
                    int32_t iPiece,
                    FXTEXT_CHARPOS* pCharPos,
                    const CFX_Matrix& tmDoc2Device);
  void RenderPath(IFDE_RenderDevice* pDevice,
                  IFDE_Pen* pPen,
                  CXFA_PieceLine* pPieceLine,
                  int32_t iPiece,
                  FXTEXT_CHARPOS* pCharPos,
                  const CFX_Matrix& tmDoc2Device);
  int32_t GetDisplayPos(const XFA_TextPiece* pPiece,
                        FXTEXT_CHARPOS* pCharPos,
                        FX_BOOL bCharCode = FALSE);
  FX_BOOL ToRun(const XFA_TextPiece* pPiece, FX_RTFTEXTOBJ& tr);
  void DoTabstops(IFDE_CSSComputedStyle* pStyle, CXFA_PieceLine* pPieceLine);
  FX_BOOL Layout(int32_t iBlock);
  int32_t CountBlocks() const;

  IXFA_TextProvider* m_pTextProvider;
  CXFA_Node* m_pTextDataNode;
  FX_BOOL m_bRichText;
  IFX_MEMAllocator* m_pAllocator;
  IFX_RTFBreak* m_pBreak;
  CXFA_LoaderContext* m_pLoader;
  int32_t m_iLines;
  FX_FLOAT m_fMaxWidth;
  CXFA_TextParser m_textParser;
  CXFA_PieceLineArray m_pieceLines;
  CXFA_TextTabstopsContext* m_pTabstopContext;
  FX_BOOL m_bBlockContinue;
};

#endif  // XFA_FXFA_APP_XFA_TEXTLAYOUT_H_