// 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/fde/cfde_txtedtengine.h"

#include <algorithm>
#include <utility>

#include "core/fxcrt/ifx_chariter.h"
#include "third_party/base/ptr_util.h"
#include "xfa/fde/cfde_textout.h"
#include "xfa/fde/cfde_txtedtbuf.h"
#include "xfa/fde/cfde_txtedtpage.h"
#include "xfa/fde/cfde_txtedtparag.h"
#include "xfa/fgas/layout/cfx_txtbreak.h"
#include "xfa/fwl/cfwl_edit.h"

namespace {

const uint32_t kPageWidthMax = 0xffff;
const uint32_t kUnicodeParagraphSeparator = 0x2029;

enum FDE_TXTEDT_MODIFY_RET {
  FDE_TXTEDT_MODIFY_RET_F_Locked = -5,
  FDE_TXTEDT_MODIFY_RET_F_Invalidate = -4,
  FDE_TXTEDT_MODIFY_RET_F_Boundary = -3,
  FDE_TXTEDT_MODIFY_RET_F_Full = -2,
  FDE_TXTEDT_MODIFY_RET_S_Normal = 0,
  FDE_TXTEDT_MODIFY_RET_S_Part = 2,
};

enum FDE_TXTEDIT_LINEEND {
  FDE_TXTEDIT_LINEEND_Auto,
  FDE_TXTEDIT_LINEEND_CRLF,
  FDE_TXTEDIT_LINEEND_CR,
  FDE_TXTEDIT_LINEEND_LF,
};

class InsertOperation : public IFDE_TxtEdtDoRecord {
 public:
  InsertOperation(CFDE_TxtEdtEngine* pEngine,
                  int32_t nCaret,
                  const CFX_WideString& str)
      : m_pEngine(pEngine), m_nCaret(nCaret), m_wsInsert(str) {
    ASSERT(m_pEngine);
  }

  ~InsertOperation() override {}

  void Undo() const override {
    if (m_pEngine->IsSelect())
      m_pEngine->ClearSelection();

    m_pEngine->Inner_DeleteRange(m_nCaret, m_wsInsert.GetLength());
    m_pEngine->UpdateChangeInfoDelete(FDE_TXTEDT_TEXTCHANGE_TYPE_Delete,
                                      m_wsInsert);
    m_pEngine->GetParams()->pEventSink->OnTextChanged(
        *m_pEngine->GetChangeInfo());
    m_pEngine->SetCaretPos(m_nCaret, true);
  }

  void Redo() const override {
    m_pEngine->Inner_Insert(m_nCaret, m_wsInsert.c_str(),
                            m_wsInsert.GetLength());
    m_pEngine->UpdateChangeInfoInsert(FDE_TXTEDT_TEXTCHANGE_TYPE_Insert,
                                      m_wsInsert);
    m_pEngine->GetParams()->pEventSink->OnTextChanged(
        *m_pEngine->GetChangeInfo());
    m_pEngine->SetCaretPos(m_nCaret, false);
  }

 private:
  CFDE_TxtEdtEngine* m_pEngine;
  int32_t m_nCaret;
  CFX_WideString m_wsInsert;
};

class DeleteOperation : public IFDE_TxtEdtDoRecord {
 public:
  DeleteOperation(CFDE_TxtEdtEngine* pEngine,
                  int32_t nIndex,
                  int32_t nCaret,
                  const CFX_WideString& wsRange,
                  bool bSel)
      : m_pEngine(pEngine),
        m_bSel(bSel),
        m_nIndex(nIndex),
        m_nCaret(nCaret),
        m_wsRange(wsRange) {
    ASSERT(m_pEngine);
  }

  ~DeleteOperation() override {}

  void Undo() const override {
    if (m_pEngine->IsSelect())
      m_pEngine->ClearSelection();

    m_pEngine->Inner_Insert(m_nIndex, m_wsRange.c_str(), m_wsRange.GetLength());
    if (m_bSel)
      m_pEngine->AddSelRange(m_nIndex, m_wsRange.GetLength());

    m_pEngine->UpdateChangeInfoInsert(FDE_TXTEDT_TEXTCHANGE_TYPE_Insert,
                                      m_wsRange);
    m_pEngine->GetParams()->pEventSink->OnTextChanged(
        *m_pEngine->GetChangeInfo());
    m_pEngine->SetCaretPos(m_nCaret, true);
  }

  void Redo() const override {
    m_pEngine->Inner_DeleteRange(m_nIndex, m_wsRange.GetLength());
    if (m_bSel)
      m_pEngine->RemoveSelRange(m_nIndex, m_wsRange.GetLength());

    m_pEngine->UpdateChangeInfoDelete(FDE_TXTEDT_TEXTCHANGE_TYPE_Delete,
                                      m_wsRange);
    m_pEngine->GetParams()->pEventSink->OnTextChanged(
        *m_pEngine->GetChangeInfo());
    m_pEngine->SetCaretPos(m_nIndex, true);
  }

 private:
  CFDE_TxtEdtEngine* m_pEngine;
  bool m_bSel;
  int32_t m_nIndex;
  int32_t m_nCaret;
  CFX_WideString m_wsRange;
};

}  // namespace

FDE_TXTEDTPARAMS::FDE_TXTEDTPARAMS()
    : fPlateWidth(0),
      fPlateHeight(0),
      nLineCount(0),
      dwLayoutStyles(0),
      dwAlignment(0),
      dwMode(0),
      fFontSize(10.0f),
      dwFontColor(0xff000000),
      fLineSpace(10.0f),
      fTabWidth(36),
      wDefChar(0xFEFF),
      wLineBreakChar('\n'),
      nLineEnd(0),
      nHorzScale(100),
      fCharSpace(0),
      pEventSink(nullptr) {}

FDE_TXTEDTPARAMS::~FDE_TXTEDTPARAMS() {}

FDE_TXTEDT_TEXTCHANGE_INFO::FDE_TXTEDT_TEXTCHANGE_INFO() {}

FDE_TXTEDT_TEXTCHANGE_INFO::~FDE_TXTEDT_TEXTCHANGE_INFO() {}

CFDE_TxtEdtEngine::CFDE_TxtEdtEngine()
    : m_pTxtBuf(pdfium::MakeUnique<CFDE_TxtEdtBuf>()),
      m_nPageLineCount(20),
      m_nLineCount(0),
      m_nAnchorPos(-1),
      m_fCaretPosReserve(0.0),
      m_nCaret(0),
      m_bBefore(true),
      m_nCaretPage(0),
      m_bLock(false),
      m_nLimit(0),
      m_wcAliasChar(L'*'),
      m_nFirstLineEnd(FDE_TXTEDIT_LINEEND_Auto),
      m_bAutoLineEnd(true),
      m_wLineEnd(kUnicodeParagraphSeparator) {
  m_bAutoLineEnd = (m_Param.nLineEnd == FDE_TXTEDIT_LINEEND_Auto);
}

CFDE_TxtEdtEngine::~CFDE_TxtEdtEngine() {
  RemoveAllParags();
  RemoveAllPages();
  m_Param.pEventSink = nullptr;
  ClearSelection();
}

void CFDE_TxtEdtEngine::SetEditParams(const FDE_TXTEDTPARAMS& params) {
  if (!m_pTextBreak)
    m_pTextBreak = pdfium::MakeUnique<CFX_TxtBreak>();

  m_Param = params;
  m_wLineEnd = params.wLineBreakChar;
  m_bAutoLineEnd = m_Param.nLineEnd == FDE_TXTEDIT_LINEEND_Auto;
  UpdateTxtBreak();
}

CFDE_TxtEdtPage* CFDE_TxtEdtEngine::GetPage(int32_t nIndex) {
  if (!pdfium::IndexInBounds(m_PagePtrArray, nIndex))
    return nullptr;
  return m_PagePtrArray[nIndex].get();
}

void CFDE_TxtEdtEngine::SetText(const CFX_WideString& wsText) {
  ResetEngine();
  int32_t nLength = wsText.GetLength();
  if (nLength > 0) {
    CFX_WideString wsTemp;
    wchar_t* lpBuffer = wsTemp.GetBuffer(nLength);
    memcpy(lpBuffer, wsText.c_str(), nLength * sizeof(wchar_t));
    ReplaceParagEnd(lpBuffer, nLength, false);
    wsTemp.ReleaseBuffer(nLength);
    if (m_nLimit > 0 && nLength > m_nLimit) {
      wsTemp.Delete(m_nLimit, nLength - m_nLimit);
      nLength = m_nLimit;
    }
    m_pTxtBuf->SetText(wsTemp);
  }
  m_pTxtBuf->Insert(nLength, &m_wLineEnd, 1);
  RebuildParagraphs();
}

CFX_WideString CFDE_TxtEdtEngine::GetText(int32_t nStart,
                                          int32_t nCount) const {
  int32_t nTextBufLength = GetTextLength();
  if (nCount == -1)
    nCount = nTextBufLength - nStart;

  CFX_WideString wsText = m_pTxtBuf->GetRange(nStart, nCount);
  RecoverParagEnd(wsText);
  return wsText;
}

void CFDE_TxtEdtEngine::ClearText() {
  if (IsLocked())
    return;

  int32_t len = GetTextLength();
  if (len == 0)
    return;
  if (m_Param.dwMode & FDE_TEXTEDITMODE_Validate) {
    CFX_WideString wsText = GetPreDeleteText(0, len);
    if (!m_Param.pEventSink->OnValidate(wsText))
      return;
  }

  DeleteRange_DoRecord(0, len, false);
  m_Param.pEventSink->OnTextChanged(m_ChangeInfo);
  SetCaretPos(0, true);
}

int32_t CFDE_TxtEdtEngine::SetCaretPos(int32_t nIndex, bool bBefore) {
  if (IsLocked())
    return 0;

  ASSERT(nIndex >= 0 && nIndex <= GetTextLength());
  if (!pdfium::IndexInBounds(m_PagePtrArray, m_nCaretPage))
    return 0;

  m_bBefore = bBefore;
  m_nCaret = nIndex;
  MovePage2Char(m_nCaret);
  GetCaretRect(m_rtCaret, m_nCaretPage, m_nCaret, m_bBefore);
  if (!m_bBefore) {
    m_nCaret++;
    m_bBefore = true;
  }
  m_fCaretPosReserve = m_rtCaret.left;
  m_Param.pEventSink->OnCaretChanged();
  m_nAnchorPos = -1;
  return m_nCaret;
}

int32_t CFDE_TxtEdtEngine::MoveCaretPos(FDE_TXTEDTMOVECARET eMoveCaret,
                                        bool bShift,
                                        bool bCtrl) {
  if (IsLocked() || !pdfium::IndexInBounds(m_PagePtrArray, m_nCaretPage))
    return 0;

  bool bSelChange = false;
  if (IsSelect()) {
    ClearSelection();
    bSelChange = true;
  }
  if (bShift) {
    if (m_nAnchorPos == -1)
      m_nAnchorPos = m_nCaret;
  } else {
    m_nAnchorPos = -1;
  }

  switch (eMoveCaret) {
    case MC_Left: {
      bool bBefore = true;
      int32_t nIndex = MoveBackward(bBefore);
      if (nIndex >= 0)
        UpdateCaretRect(nIndex, bBefore);
      break;
    }
    case MC_Right: {
      bool bBefore = true;
      int32_t nIndex = MoveForward(bBefore);
      if (nIndex >= 0)
        UpdateCaretRect(nIndex, bBefore);
      break;
    }
    case MC_Up: {
      CFX_PointF ptCaret;
      if (MoveUp(ptCaret))
        UpdateCaretIndex(ptCaret);
      break;
    }
    case MC_Down: {
      CFX_PointF ptCaret;
      if (MoveDown(ptCaret))
        UpdateCaretIndex(ptCaret);
      break;
    }
    case MC_LineStart:
      MoveLineStart();
      break;
    case MC_LineEnd:
      MoveLineEnd();
      break;
    case MC_Home:
      MoveHome();
      break;
    case MC_End:
      MoveEnd();
      break;
    default:
      break;
  }
  if (bShift && m_nAnchorPos != -1 && (m_nAnchorPos != m_nCaret)) {
    AddSelRange(std::min(m_nAnchorPos, m_nCaret), abs(m_nAnchorPos - m_nCaret));
    m_Param.pEventSink->OnSelChanged();
  }
  if (bSelChange)
    m_Param.pEventSink->OnSelChanged();

  return m_nCaret;
}

int32_t CFDE_TxtEdtEngine::Insert(int32_t nStart,
                                  const wchar_t* lpText,
                                  int32_t nLength) {
  if (IsLocked())
    return FDE_TXTEDT_MODIFY_RET_F_Locked;

  CFX_WideString wsTemp;
  wchar_t* lpBuffer = wsTemp.GetBuffer(nLength);
  memcpy(lpBuffer, lpText, nLength * sizeof(wchar_t));
  ReplaceParagEnd(lpBuffer, nLength, false);
  wsTemp.ReleaseBuffer(nLength);
  bool bPart = false;
  if (m_nLimit > 0) {
    int32_t nTotalLength = GetTextLength();
    for (const auto& lpSelRange : m_SelRangePtrArr)
      nTotalLength -= lpSelRange->nCount;

    int32_t nExpectLength = nTotalLength + nLength;
    if (nTotalLength == m_nLimit)
      return FDE_TXTEDT_MODIFY_RET_F_Full;

    if (nExpectLength > m_nLimit) {
      nLength -= (nExpectLength - m_nLimit);
      bPart = true;
    }
  }
  if ((m_Param.dwMode & FDE_TEXTEDITMODE_LimitArea_Vert) ||
      (m_Param.dwMode & FDE_TEXTEDITMODE_LimitArea_Horz)) {
    int32_t nTemp = nLength;
    if (m_Param.dwMode & FDE_TEXTEDITMODE_Password) {
      while (nLength > 0) {
        CFX_WideString wsText = GetPreInsertText(m_nCaret, lpBuffer, nLength);
        int32_t nTotal = wsText.GetLength();
        wchar_t* lpBuf = wsText.GetBuffer(nTotal);
        for (int32_t i = 0; i < nTotal; i++) {
          lpBuf[i] = m_wcAliasChar;
        }
        wsText.ReleaseBuffer(nTotal);
        if (IsFitArea(wsText)) {
          break;
        }
        nLength--;
      }
    } else {
      while (nLength > 0) {
        CFX_WideString wsText = GetPreInsertText(m_nCaret, lpBuffer, nLength);
        if (IsFitArea(wsText)) {
          break;
        }
        nLength--;
      }
    }
    if (nLength == 0) {
      return FDE_TXTEDT_MODIFY_RET_F_Full;
    }
    if (nLength < nTemp) {
      bPart = true;
    }
  }
  if (m_Param.dwMode & FDE_TEXTEDITMODE_Validate) {
    CFX_WideString wsText = GetPreInsertText(m_nCaret, lpBuffer, nLength);
    if (!m_Param.pEventSink->OnValidate(wsText))
      return FDE_TXTEDT_MODIFY_RET_F_Invalidate;
  }
  if (IsSelect()) {
    DeleteSelect();
  }
  m_Param.pEventSink->OnAddDoRecord(pdfium::MakeUnique<InsertOperation>(
      this, m_nCaret, CFX_WideString(lpBuffer, nLength)));

  m_ChangeInfo.wsPrevText = GetText(0, -1);
  Inner_Insert(m_nCaret, lpBuffer, nLength);
  m_ChangeInfo.nChangeType = FDE_TXTEDT_TEXTCHANGE_TYPE_Insert;
  m_ChangeInfo.wsInsert = CFX_WideString(lpBuffer, nLength);
  nStart = m_nCaret;
  nStart += nLength;
  wchar_t wChar = m_pTxtBuf->GetCharByIndex(nStart - 1);
  bool bBefore = true;
  if (wChar != L'\n' && wChar != L'\r') {
    nStart--;
    bBefore = false;
  }
  SetCaretPos(nStart, bBefore);
  m_Param.pEventSink->OnTextChanged(m_ChangeInfo);
  return bPart ? FDE_TXTEDT_MODIFY_RET_S_Part : FDE_TXTEDT_MODIFY_RET_S_Normal;
}

int32_t CFDE_TxtEdtEngine::Delete(int32_t nStart, bool bBackspace) {
  if (IsLocked()) {
    return FDE_TXTEDT_MODIFY_RET_F_Locked;
  }
  if (IsSelect()) {
    DeleteSelect();
    return FDE_TXTEDT_MODIFY_RET_S_Normal;
  }

  int32_t nCount = 1;
  if (bBackspace) {
    if (nStart == 0) {
      return FDE_TXTEDT_MODIFY_RET_F_Boundary;
    }
    if (nStart > 2 && m_pTxtBuf->GetCharByIndex(nStart - 1) == L'\n' &&
        m_pTxtBuf->GetCharByIndex(nStart - 2) == L'\r') {
      nStart--;
      nCount++;
    }
    nStart--;
  } else {
    if (nStart == GetTextLength()) {
      return FDE_TXTEDT_MODIFY_RET_F_Full;
    }
    if ((nStart + 1 < GetTextLength()) &&
        (m_pTxtBuf->GetCharByIndex(nStart) == L'\r') &&
        (m_pTxtBuf->GetCharByIndex(nStart + 1) == L'\n')) {
      nCount++;
    }
  }
  if (m_Param.dwMode & FDE_TEXTEDITMODE_Validate) {
    CFX_WideString wsText = GetPreDeleteText(nStart, nCount);
    if (!m_Param.pEventSink->OnValidate(wsText))
      return FDE_TXTEDT_MODIFY_RET_F_Invalidate;
  }
  CFX_WideString wsRange = m_pTxtBuf->GetRange(nStart, nCount);
  m_Param.pEventSink->OnAddDoRecord(pdfium::MakeUnique<DeleteOperation>(
      this, nStart, m_nCaret, wsRange, false));

  UpdateChangeInfoDelete(FDE_TXTEDT_TEXTCHANGE_TYPE_Delete,
                         GetText(nStart, nCount));
  Inner_DeleteRange(nStart, nCount);
  SetCaretPos(nStart + ((!bBackspace && nStart > 0) ? -1 : 0),
              (bBackspace || nStart == 0));
  m_Param.pEventSink->OnTextChanged(m_ChangeInfo);
  return FDE_TXTEDT_MODIFY_RET_S_Normal;
}

void CFDE_TxtEdtEngine::RemoveSelRange(int32_t nStart, int32_t nCount) {
  int32_t nRangeCount = pdfium::CollectionSize<int32_t>(m_SelRangePtrArr);
  for (int32_t i = 0; i < nRangeCount; i++) {
    FDE_TXTEDTSELRANGE* lpTemp = m_SelRangePtrArr[i].get();
    if (lpTemp->nStart == nStart && lpTemp->nCount == nCount) {
      m_SelRangePtrArr.erase(m_SelRangePtrArr.begin() + i);
      return;
    }
  }
}

void CFDE_TxtEdtEngine::AddSelRange(int32_t nStart, int32_t nCount) {
  if (nCount == -1)
    nCount = GetTextLength() - nStart;

  if (m_SelRangePtrArr.empty()) {
    auto lpSelRange = pdfium::MakeUnique<FDE_TXTEDTSELRANGE>();
    lpSelRange->nStart = nStart;
    lpSelRange->nCount = nCount;
    m_SelRangePtrArr.push_back(std::move(lpSelRange));
    m_Param.pEventSink->OnSelChanged();
    return;
  }
  auto* lpTemp = m_SelRangePtrArr.back().get();
  if (nStart >= lpTemp->nStart + lpTemp->nCount) {
    auto lpSelRange = pdfium::MakeUnique<FDE_TXTEDTSELRANGE>();
    lpSelRange->nStart = nStart;
    lpSelRange->nCount = nCount;
    m_SelRangePtrArr.push_back(std::move(lpSelRange));
    m_Param.pEventSink->OnSelChanged();
    return;
  }
  int32_t nEnd = nStart + nCount - 1;
  bool bBegin = false;
  int32_t nRangeBgn = 0;
  int32_t nRangeCnt = 0;
  for (int32_t i = 0, nSize = pdfium::CollectionSize<int32_t>(m_SelRangePtrArr);
       i < nSize; i++) {
    lpTemp = m_SelRangePtrArr[i].get();
    int32_t nTempBgn = lpTemp->nStart;
    int32_t nTempEnd = nTempBgn + lpTemp->nCount - 1;
    if (bBegin) {
      if (nEnd < nTempBgn) {
        break;
      } else if (nStart >= nTempBgn && nStart <= nTempEnd) {
        nRangeCnt++;
        break;
      }
      nRangeCnt++;
    } else {
      if (nStart <= nTempEnd) {
        nRangeBgn = i;
        if (nEnd < nTempBgn) {
          break;
        }
        nRangeCnt = 1;
        bBegin = true;
      }
    }
  }
  if (nRangeCnt == 0) {
    auto lpSelRange = pdfium::MakeUnique<FDE_TXTEDTSELRANGE>();
    lpSelRange->nStart = nStart;
    lpSelRange->nCount = nCount;
    m_SelRangePtrArr.insert(m_SelRangePtrArr.begin() + nRangeBgn,
                            std::move(lpSelRange));
  } else {
    lpTemp = m_SelRangePtrArr[nRangeBgn].get();
    lpTemp->nStart = nStart;
    lpTemp->nCount = nCount;
    nRangeCnt--;
    nRangeBgn++;
    m_SelRangePtrArr.erase(m_SelRangePtrArr.begin() + nRangeBgn,
                           m_SelRangePtrArr.begin() + nRangeBgn + nRangeCnt);
  }
  m_Param.pEventSink->OnSelChanged();
}

int32_t CFDE_TxtEdtEngine::GetSelRange(int32_t nIndex, int32_t* nStart) const {
  if (nStart)
    *nStart = m_SelRangePtrArr[nIndex]->nStart;
  return m_SelRangePtrArr[nIndex]->nCount;
}

void CFDE_TxtEdtEngine::ClearSelection() {
  if (m_SelRangePtrArr.empty())
    return;
  m_SelRangePtrArr.clear();
  if (m_Param.pEventSink)
    m_Param.pEventSink->OnSelChanged();
}

bool CFDE_TxtEdtEngine::Redo(const IFDE_TxtEdtDoRecord* pDoRecord) {
  if (IsLocked())
    return false;
  pDoRecord->Redo();
  return true;
}

bool CFDE_TxtEdtEngine::Undo(const IFDE_TxtEdtDoRecord* pDoRecord) {
  if (IsLocked())
    return false;
  pDoRecord->Undo();
  return true;
}

void CFDE_TxtEdtEngine::Layout() {
  CFX_AutoRestorer<bool> lock(&m_bLock);
  m_bLock = true;

  RemoveAllPages();
  UpdateLineCounts();
  UpdatePages();

  m_nCaret = std::min(m_nCaret, GetTextLength());
  m_rtCaret = CFX_RectF(0, 0, 1, m_Param.fFontSize);
}

int32_t CFDE_TxtEdtEngine::Line2Parag(int32_t nStartParag,
                                      int32_t nStartLineofParag,
                                      int32_t nLineIndex,
                                      int32_t& nStartLine) const {
  int32_t nLineTotal = nStartLineofParag;
  int32_t nCount = pdfium::CollectionSize<int32_t>(m_ParagPtrArray);
  CFDE_TxtEdtParag* pParag = nullptr;
  int32_t i = nStartParag;
  for (; i < nCount; i++) {
    pParag = m_ParagPtrArray[i].get();
    nLineTotal += pParag->GetLineCount();
    if (nLineTotal > nLineIndex) {
      break;
    }
  }
  nStartLine = nLineTotal - pParag->GetLineCount();
  return i;
}

CFX_WideString CFDE_TxtEdtEngine::GetPreDeleteText(int32_t nIndex,
                                                   int32_t nLength) {
  CFX_WideString wsText = GetText(0, GetTextLength());
  wsText.Delete(nIndex, nLength);
  return wsText;
}

CFX_WideString CFDE_TxtEdtEngine::GetPreInsertText(int32_t nIndex,
                                                   const wchar_t* lpText,
                                                   int32_t nLength) {
  CFX_WideString wsText = GetText(0, GetTextLength());
  int32_t nSelIndex = 0;
  int32_t nSelLength = 0;
  int32_t nSelCount = CountSelRanges();
  while (nSelCount--) {
    nSelLength = GetSelRange(nSelCount, &nSelIndex);
    wsText.Delete(nSelIndex, nSelLength);
    nIndex = nSelIndex;
  }
  CFX_WideString wsTemp;
  int32_t nOldLength = wsText.GetLength();
  const wchar_t* pOldBuffer = wsText.c_str();
  wchar_t* lpBuffer = wsTemp.GetBuffer(nOldLength + nLength);
  memcpy(lpBuffer, pOldBuffer, (nIndex) * sizeof(wchar_t));
  memcpy(lpBuffer + nIndex, lpText, nLength * sizeof(wchar_t));
  memcpy(lpBuffer + nIndex + nLength, pOldBuffer + nIndex,
         (nOldLength - nIndex) * sizeof(wchar_t));
  wsTemp.ReleaseBuffer(nOldLength + nLength);
  wsText = wsTemp;
  return wsText;
}

CFX_WideString CFDE_TxtEdtEngine::GetPreReplaceText(int32_t nIndex,
                                                    int32_t nOriginLength,
                                                    const wchar_t* lpText,
                                                    int32_t nLength) {
  CFX_WideString wsText = GetText(0, GetTextLength());
  int32_t nSelIndex = 0;
  int32_t nSelLength = 0;
  int32_t nSelCount = CountSelRanges();
  while (nSelCount--) {
    nSelLength = GetSelRange(nSelCount, &nSelIndex);
    wsText.Delete(nSelIndex, nSelLength);
  }
  wsText.Delete(nIndex, nOriginLength);
  int32_t i = 0;
  for (i = 0; i < nLength; i++)
    wsText.Insert(nIndex++, lpText[i]);

  return wsText;
}

void CFDE_TxtEdtEngine::Inner_Insert(int32_t nStart,
                                     const wchar_t* lpText,
                                     int32_t nLength) {
  ASSERT(nLength > 0);
  FDE_TXTEDTPARAGPOS ParagPos;
  TextPos2ParagPos(nStart, ParagPos);
  m_Param.pEventSink->OnPageUnload(m_nCaretPage);
  int32_t nParagCount = pdfium::CollectionSize<int32_t>(m_ParagPtrArray);
  for (int32_t i = ParagPos.nParagIndex + 1; i < nParagCount; i++)
    m_ParagPtrArray[i]->IncrementStartIndex(nLength);

  CFDE_TxtEdtParag* pParag = m_ParagPtrArray[ParagPos.nParagIndex].get();
  int32_t nReserveLineCount = pParag->GetLineCount();
  int32_t nReserveCharStart = pParag->GetStartIndex();
  int32_t nLeavePart = ParagPos.nCharIndex;
  int32_t nCutPart = pParag->GetTextLength() - ParagPos.nCharIndex;
  int32_t nTextStart = 0;
  wchar_t wCurChar = L' ';
  const wchar_t* lpPos = lpText;
  bool bFirst = true;
  int32_t nParagIndex = ParagPos.nParagIndex;
  for (int32_t i = 0; i < nLength; i++, lpPos++) {
    wCurChar = *lpPos;
    if (wCurChar == m_wLineEnd) {
      if (bFirst) {
        pParag->SetTextLength(nLeavePart + (i - nTextStart + 1));
        pParag->SetLineCount(-1);
        nReserveCharStart += pParag->GetTextLength();
        bFirst = false;
      } else {
        auto pParag2 = pdfium::MakeUnique<CFDE_TxtEdtParag>(this);
        pParag2->SetLineCount(-1);
        pParag2->SetTextLength(i - nTextStart + 1);
        pParag2->SetStartIndex(nReserveCharStart);
        nReserveCharStart += pParag2->GetTextLength();
        m_ParagPtrArray.insert(m_ParagPtrArray.begin() + ++nParagIndex,
                               std::move(pParag2));
      }
      nTextStart = i + 1;
    }
  }
  if (bFirst) {
    pParag->IncrementTextLength(nLength);
    pParag->SetLineCount(-1);
    bFirst = false;
  } else {
    auto pParag2 = pdfium::MakeUnique<CFDE_TxtEdtParag>(this);
    pParag2->SetLineCount(-1);
    pParag2->SetTextLength(nLength - nTextStart + nCutPart);
    pParag2->SetStartIndex(nReserveCharStart);
    m_ParagPtrArray.insert(m_ParagPtrArray.begin() + ++nParagIndex,
                           std::move(pParag2));
  }
  m_pTxtBuf->Insert(nStart, lpText, nLength);
  int32_t nTotalLineCount = 0;
  for (int32_t i = ParagPos.nParagIndex; i <= nParagIndex; i++) {
    pParag = m_ParagPtrArray[i].get();
    pParag->CalcLines();
    nTotalLineCount += pParag->GetLineCount();
  }
  m_nLineCount += nTotalLineCount - nReserveLineCount;
  m_Param.pEventSink->OnPageLoad(m_nCaretPage);
  UpdatePages();
}

void CFDE_TxtEdtEngine::Inner_DeleteRange(int32_t nStart, int32_t nCount) {
  if (nCount == -1) {
    nCount = m_pTxtBuf->GetTextLength() - nStart;
  }
  int32_t nEnd = nStart + nCount - 1;
  ASSERT(nStart >= 0 && nEnd < m_pTxtBuf->GetTextLength());
  m_Param.pEventSink->OnPageUnload(m_nCaretPage);
  FDE_TXTEDTPARAGPOS ParagPosBgn, ParagPosEnd;
  TextPos2ParagPos(nStart, ParagPosBgn);
  TextPos2ParagPos(nEnd, ParagPosEnd);
  CFDE_TxtEdtParag* pParag = m_ParagPtrArray[ParagPosEnd.nParagIndex].get();
  bool bLastParag = false;
  if (ParagPosEnd.nCharIndex == pParag->GetTextLength() - 1) {
    if (ParagPosEnd.nParagIndex <
        pdfium::CollectionSize<int32_t>(m_ParagPtrArray) - 1) {
      ParagPosEnd.nParagIndex++;
    } else {
      bLastParag = true;
    }
  }
  int32_t nTotalLineCount = 0;
  int32_t nTotalCharCount = 0;
  int32_t i = 0;
  for (i = ParagPosBgn.nParagIndex; i <= ParagPosEnd.nParagIndex; i++) {
    CFDE_TxtEdtParag* pTextParag = m_ParagPtrArray[i].get();
    pTextParag->CalcLines();
    nTotalLineCount += pTextParag->GetLineCount();
    nTotalCharCount += pTextParag->GetTextLength();
  }
  m_pTxtBuf->Delete(nStart, nCount);
  int32_t nNextParagIndex = (ParagPosBgn.nCharIndex == 0 && bLastParag)
                                ? ParagPosBgn.nParagIndex
                                : (ParagPosBgn.nParagIndex + 1);
  m_ParagPtrArray.erase(m_ParagPtrArray.begin() + nNextParagIndex,
                        m_ParagPtrArray.begin() + ParagPosEnd.nParagIndex + 1);

  if (!(bLastParag && ParagPosBgn.nCharIndex == 0)) {
    pParag = m_ParagPtrArray[ParagPosBgn.nParagIndex].get();
    pParag->SetTextLength(nTotalCharCount - nCount);
    pParag->CalcLines();
    nTotalLineCount -= pParag->GetTextLength();
  }
  int32_t nParagCount = pdfium::CollectionSize<int32_t>(m_ParagPtrArray);
  for (i = nNextParagIndex; i < nParagCount; i++)
    m_ParagPtrArray[i]->DecrementStartIndex(nCount);

  m_nLineCount -= nTotalLineCount;
  UpdatePages();
  int32_t nPageCount = CountPages();
  if (m_nCaretPage >= nPageCount) {
    m_nCaretPage = nPageCount - 1;
  }
  m_Param.pEventSink->OnPageLoad(m_nCaretPage);
}

void CFDE_TxtEdtEngine::DeleteRange_DoRecord(int32_t nStart,
                                             int32_t nCount,
                                             bool bSel) {
  ASSERT(nStart >= 0);
  if (nCount == -1) {
    nCount = GetTextLength() - nStart;
  }
  ASSERT((nStart + nCount) <= m_pTxtBuf->GetTextLength());

  CFX_WideString wsRange = m_pTxtBuf->GetRange(nStart, nCount);
  m_Param.pEventSink->OnAddDoRecord(pdfium::MakeUnique<DeleteOperation>(
      this, nStart, m_nCaret, wsRange, bSel));

  UpdateChangeInfoDelete(FDE_TXTEDT_TEXTCHANGE_TYPE_Delete,
                         GetText(nStart, nCount));
  Inner_DeleteRange(nStart, nCount);
}

void CFDE_TxtEdtEngine::ResetEngine() {
  RemoveAllPages();
  RemoveAllParags();
  ClearSelection();
  m_nCaret = 0;
  m_pTxtBuf->Clear(false);
  m_nCaret = 0;
}

void CFDE_TxtEdtEngine::RebuildParagraphs() {
  RemoveAllParags();
  wchar_t wChar = L' ';
  int32_t nParagStart = 0;
  int32_t nIndex = 0;
  auto pIter = pdfium::MakeUnique<CFDE_TxtEdtBuf::Iterator>(m_pTxtBuf.get(), 0);
  pIter->SetAt(0);
  do {
    wChar = pIter->GetChar();
    nIndex = pIter->GetAt();
    if (wChar == m_wLineEnd) {
      auto pParag = pdfium::MakeUnique<CFDE_TxtEdtParag>(this);
      pParag->SetStartIndex(nParagStart);
      pParag->SetTextLength(nIndex - nParagStart + 1);
      pParag->SetLineCount(-1);
      m_ParagPtrArray.push_back(std::move(pParag));
      nParagStart = nIndex + 1;
    }
  } while (pIter->Next());
}

void CFDE_TxtEdtEngine::UpdateLineCounts() {
  if (m_ParagPtrArray.empty())
    return;

  int32_t nLineCount = 0;
  for (auto& pParag : m_ParagPtrArray) {
    pParag->CalcLines();
    nLineCount += pParag->GetLineCount();
  }
  m_nLineCount = nLineCount;
}

void CFDE_TxtEdtEngine::UpdatePages() {
  if (m_nLineCount == 0)
    return;

  int32_t nPageCount = (m_nLineCount - 1) / (m_nPageLineCount) + 1;
  int32_t nSize = pdfium::CollectionSize<int32_t>(m_PagePtrArray);
  if (nSize == nPageCount)
    return;

  if (nSize > nPageCount) {
    m_PagePtrArray.erase(m_PagePtrArray.begin() + nPageCount,
                         m_PagePtrArray.end());
    return;
  }
  for (int32_t i = nSize; i < nPageCount; i++)
    m_PagePtrArray.push_back(pdfium::MakeUnique<CFDE_TxtEdtPage>(this, i));
}

void CFDE_TxtEdtEngine::UpdateTxtBreak() {
  uint32_t dwStyle = m_pTextBreak->GetLayoutStyles();
  if (m_Param.dwMode & FDE_TEXTEDITMODE_MultiLines)
    dwStyle &= ~FX_LAYOUTSTYLE_SingleLine;
  else
    dwStyle |= FX_LAYOUTSTYLE_SingleLine;

  if (m_Param.dwLayoutStyles & FDE_TEXTEDITLAYOUT_CombText)
    dwStyle |= FX_LAYOUTSTYLE_CombText;
  else
    dwStyle &= ~FX_LAYOUTSTYLE_CombText;

  m_pTextBreak->SetLayoutStyles(dwStyle);
  uint32_t dwAligment = 0;
  if (m_Param.dwAlignment & FDE_TEXTEDITALIGN_Justified)
    dwAligment |= CFX_TxtLineAlignment_Justified;

  if (m_Param.dwAlignment & FDE_TEXTEDITALIGN_Center)
    dwAligment |= CFX_TxtLineAlignment_Center;
  else if (m_Param.dwAlignment & FDE_TEXTEDITALIGN_Right)
    dwAligment |= CFX_TxtLineAlignment_Right;

  m_pTextBreak->SetAlignment(dwAligment);

  if (m_Param.dwMode & FDE_TEXTEDITMODE_AutoLineWrap)
    m_pTextBreak->SetLineWidth(m_Param.fPlateWidth);
  else
    m_pTextBreak->SetLineWidth(kPageWidthMax);

  m_nPageLineCount = m_Param.nLineCount;
  if (m_Param.dwLayoutStyles & FDE_TEXTEDITLAYOUT_CombText) {
    float fCombWidth = m_Param.fPlateWidth;
    if (m_nLimit > 0)
      fCombWidth /= m_nLimit;

    m_pTextBreak->SetCombWidth(fCombWidth);
  }
  m_pTextBreak->SetFont(m_Param.pFont);
  m_pTextBreak->SetFontSize(m_Param.fFontSize);
  m_pTextBreak->SetTabWidth(m_Param.fTabWidth);
  m_pTextBreak->SetDefaultChar(m_Param.wDefChar);
  m_pTextBreak->SetParagraphBreakChar(m_Param.wLineBreakChar);
  m_pTextBreak->SetLineBreakTolerance(m_Param.fFontSize * 0.2f);
  m_pTextBreak->SetHorizontalScale(m_Param.nHorzScale);
  m_pTextBreak->SetCharSpace(m_Param.fCharSpace);
}

bool CFDE_TxtEdtEngine::ReplaceParagEnd(wchar_t*& lpText,
                                        int32_t& nLength,
                                        bool bPreIsCR) {
  for (int32_t i = 0; i < nLength; i++) {
    wchar_t wc = lpText[i];
    switch (wc) {
      case L'\r': {
        lpText[i] = m_wLineEnd;
        bPreIsCR = true;
      } break;
      case L'\n': {
        if (bPreIsCR == true) {
          int32_t nNext = i + 1;
          if (nNext < nLength) {
            memmove(lpText + i, lpText + nNext,
                    (nLength - nNext) * sizeof(wchar_t));
          }
          i--;
          nLength--;
          bPreIsCR = false;
          if (m_bAutoLineEnd) {
            m_nFirstLineEnd = FDE_TXTEDIT_LINEEND_CRLF;
            m_bAutoLineEnd = false;
          }
        } else {
          lpText[i] = m_wLineEnd;
          if (m_bAutoLineEnd) {
            m_nFirstLineEnd = FDE_TXTEDIT_LINEEND_LF;
            m_bAutoLineEnd = false;
          }
        }
      } break;
      default: {
        if (bPreIsCR && m_bAutoLineEnd) {
          m_nFirstLineEnd = FDE_TXTEDIT_LINEEND_CR;
          m_bAutoLineEnd = false;
        }
        bPreIsCR = false;
      } break;
    }
  }
  return bPreIsCR;
}

void CFDE_TxtEdtEngine::RecoverParagEnd(CFX_WideString& wsText) const {
  wchar_t wc = (m_nFirstLineEnd == FDE_TXTEDIT_LINEEND_CR) ? L'\n' : L'\r';
  if (m_nFirstLineEnd == FDE_TXTEDIT_LINEEND_CRLF) {
    std::vector<int32_t> PosArr;
    int32_t nLength = wsText.GetLength();
    wchar_t* lpPos = const_cast<wchar_t*>(wsText.c_str());
    for (int32_t i = 0; i < nLength; i++, lpPos++) {
      if (*lpPos == m_wLineEnd) {
        *lpPos = wc;
        PosArr.push_back(i);
      }
    }
    const wchar_t* lpSrcBuf = wsText.c_str();
    CFX_WideString wsTemp;
    int32_t nCount = pdfium::CollectionSize<int32_t>(PosArr);
    wchar_t* lpDstBuf = wsTemp.GetBuffer(nLength + nCount);
    int32_t nDstPos = 0;
    int32_t nSrcPos = 0;
    for (int32_t i = 0; i < nCount; i++) {
      int32_t nPos = PosArr[i];
      int32_t nCopyLen = nPos - nSrcPos + 1;
      memcpy(lpDstBuf + nDstPos, lpSrcBuf + nSrcPos,
             nCopyLen * sizeof(wchar_t));
      nDstPos += nCopyLen;
      nSrcPos += nCopyLen;
      lpDstBuf[nDstPos] = L'\n';
      nDstPos++;
    }
    if (nSrcPos < nLength) {
      memcpy(lpDstBuf + nDstPos, lpSrcBuf + nSrcPos,
             (nLength - nSrcPos) * sizeof(wchar_t));
    }
    wsTemp.ReleaseBuffer(nLength + nCount);
    wsText = wsTemp;
  } else {
    int32_t nLength = wsText.GetLength();
    wchar_t* lpBuf = const_cast<wchar_t*>(wsText.c_str());
    for (int32_t i = 0; i < nLength; i++, lpBuf++) {
      if (*lpBuf == m_wLineEnd)
        *lpBuf = wc;
    }
  }
}

int32_t CFDE_TxtEdtEngine::MovePage2Char(int32_t nIndex) {
  ASSERT(nIndex >= 0);
  ASSERT(nIndex <= m_pTxtBuf->GetTextLength());
  if (m_nCaretPage >= 0) {
    CFDE_TxtEdtPage* pPage = m_PagePtrArray[m_nCaretPage].get();
    m_Param.pEventSink->OnPageLoad(m_nCaretPage);
    int32_t nPageCharStart = pPage->GetCharStart();
    int32_t nPageCharCount = pPage->GetCharCount();
    if (nIndex >= nPageCharStart && nIndex < nPageCharStart + nPageCharCount) {
      m_Param.pEventSink->OnPageUnload(m_nCaretPage);
      return m_nCaretPage;
    }
    m_Param.pEventSink->OnPageUnload(m_nCaretPage);
  }
  CFDE_TxtEdtParag* pParag = nullptr;
  int32_t nLineCount = 0;
  int32_t nParagCount = pdfium::CollectionSize<int32_t>(m_ParagPtrArray);
  int32_t i = 0;
  for (i = 0; i < nParagCount; i++) {
    pParag = m_ParagPtrArray[i].get();
    if (pParag->GetStartIndex() <= nIndex &&
        nIndex < (pParag->GetStartIndex() + pParag->GetTextLength())) {
      break;
    }
    nLineCount += pParag->GetLineCount();
  }
  pParag->LoadParag();
  int32_t nLineStart = -1;
  int32_t nLineCharCount = -1;
  for (i = 0; i < pParag->GetLineCount(); i++) {
    pParag->GetLineRange(i, nLineStart, nLineCharCount);
    if (nLineStart <= nIndex && nIndex < (nLineStart + nLineCharCount))
      break;
  }
  ASSERT(i < pParag->GetLineCount());
  nLineCount += (i + 1);
  m_nCaretPage = (nLineCount - 1) / m_nPageLineCount + 1 - 1;
  pParag->UnloadParag();
  return m_nCaretPage;
}

void CFDE_TxtEdtEngine::TextPos2ParagPos(int32_t nIndex,
                                         FDE_TXTEDTPARAGPOS& ParagPos) const {
  ASSERT(nIndex >= 0 && nIndex < m_pTxtBuf->GetTextLength());
  int32_t nCount = pdfium::CollectionSize<int32_t>(m_ParagPtrArray);
  int32_t nBgn = 0;
  int32_t nMid = 0;
  int32_t nEnd = nCount - 1;
  while (nEnd > nBgn) {
    nMid = (nBgn + nEnd) / 2;
    CFDE_TxtEdtParag* pParag = m_ParagPtrArray[nMid].get();
    if (nIndex < pParag->GetStartIndex())
      nEnd = nMid - 1;
    else if (nIndex >= (pParag->GetStartIndex() + pParag->GetTextLength()))
      nBgn = nMid + 1;
    else
      break;
  }
  if (nBgn == nEnd)
    nMid = nBgn;

  ASSERT(nIndex >= m_ParagPtrArray[nMid]->GetStartIndex() &&
         (nIndex < m_ParagPtrArray[nMid]->GetStartIndex() +
                       m_ParagPtrArray[nMid]->GetTextLength()));
  ParagPos.nParagIndex = nMid;
  ParagPos.nCharIndex = nIndex - m_ParagPtrArray[nMid]->GetStartIndex();
}

int32_t CFDE_TxtEdtEngine::MoveForward(bool& bBefore) {
  if (m_nCaret == m_pTxtBuf->GetTextLength() - 1)
    return -1;

  int32_t nCaret = m_nCaret;
  if ((nCaret + 1 < m_pTxtBuf->GetTextLength()) &&
      (m_pTxtBuf->GetCharByIndex(nCaret) == L'\r') &&
      (m_pTxtBuf->GetCharByIndex(nCaret + 1) == L'\n')) {
    nCaret++;
  }
  nCaret++;
  bBefore = true;
  return nCaret;
}

int32_t CFDE_TxtEdtEngine::MoveBackward(bool& bBefore) {
  if (m_nCaret == 0)
    return false;

  int32_t nCaret = m_nCaret;
  if (nCaret > 2 && m_pTxtBuf->GetCharByIndex(nCaret - 1) == L'\n' &&
      m_pTxtBuf->GetCharByIndex(nCaret - 2) == L'\r') {
    nCaret--;
  }
  nCaret--;
  bBefore = true;
  return nCaret;
}

bool CFDE_TxtEdtEngine::MoveUp(CFX_PointF& ptCaret) {
  CFDE_TxtEdtPage* pPage = GetPage(m_nCaretPage);
  const CFX_RectF& rtContent = pPage->GetContentsBox();
  ptCaret.x = m_fCaretPosReserve;
  ptCaret.y = m_rtCaret.top + m_rtCaret.height / 2 - m_Param.fLineSpace;
  if (ptCaret.y < rtContent.top) {
    if (m_nCaretPage == 0) {
      return false;
    }
    ptCaret.y -= rtContent.top;
    m_nCaretPage--;
    CFDE_TxtEdtPage* pCurPage = GetPage(m_nCaretPage);
    ptCaret.y += pCurPage->GetContentsBox().bottom();
  }

  return true;
}

bool CFDE_TxtEdtEngine::MoveDown(CFX_PointF& ptCaret) {
  CFDE_TxtEdtPage* pPage = GetPage(m_nCaretPage);
  const CFX_RectF& rtContent = pPage->GetContentsBox();
  ptCaret.x = m_fCaretPosReserve;
  ptCaret.y = m_rtCaret.top + m_rtCaret.height / 2 + m_Param.fLineSpace;
  if (ptCaret.y >= rtContent.bottom()) {
    if (m_nCaretPage == CountPages() - 1) {
      return false;
    }
    ptCaret.y -= rtContent.bottom();
    m_nCaretPage++;
    CFDE_TxtEdtPage* pCurPage = GetPage(m_nCaretPage);
    ptCaret.y += pCurPage->GetContentsBox().top;
  }
  return true;
}

bool CFDE_TxtEdtEngine::MoveLineStart() {
  int32_t nIndex = m_bBefore ? m_nCaret : m_nCaret - 1;
  FDE_TXTEDTPARAGPOS ParagPos;
  TextPos2ParagPos(nIndex, ParagPos);
  CFDE_TxtEdtParag* pParag = m_ParagPtrArray[ParagPos.nParagIndex].get();
  pParag->LoadParag();
  int32_t nLineCount = pParag->GetLineCount();
  int32_t i = 0;
  int32_t nStart = 0;
  int32_t nCount = 0;
  for (; i < nLineCount; i++) {
    pParag->GetLineRange(i, nStart, nCount);
    if (nIndex >= nStart && nIndex < nStart + nCount) {
      break;
    }
  }
  UpdateCaretRect(nStart, true);
  pParag->UnloadParag();
  return true;
}

bool CFDE_TxtEdtEngine::MoveLineEnd() {
  int32_t nIndex = m_bBefore ? m_nCaret : m_nCaret - 1;
  FDE_TXTEDTPARAGPOS ParagPos;
  TextPos2ParagPos(nIndex, ParagPos);
  CFDE_TxtEdtParag* pParag = m_ParagPtrArray[ParagPos.nParagIndex].get();
  pParag->LoadParag();
  int32_t nLineCount = pParag->GetLineCount();
  int32_t i = 0;
  int32_t nStart = 0;
  int32_t nCount = 0;
  for (; i < nLineCount; i++) {
    pParag->GetLineRange(i, nStart, nCount);
    if (nIndex >= nStart && nIndex < nStart + nCount) {
      break;
    }
  }
  nIndex = nStart + nCount - 1;
  ASSERT(nIndex <= GetTextLength());
  wchar_t wChar = m_pTxtBuf->GetCharByIndex(nIndex);
  bool bBefore = false;
  if (nIndex <= GetTextLength()) {
    if (wChar == L'\r') {
      bBefore = true;
    } else if (wChar == L'\n' && nIndex > nStart) {
      bBefore = true;
      nIndex--;
      wChar = m_pTxtBuf->GetCharByIndex(nIndex);
      if (wChar != L'\r') {
        nIndex++;
      }
    }
  }
  UpdateCaretRect(nIndex, bBefore);
  pParag->UnloadParag();
  return true;
}

bool CFDE_TxtEdtEngine::MoveHome() {
  UpdateCaretRect(0, true);
  return true;
}

bool CFDE_TxtEdtEngine::MoveEnd() {
  UpdateCaretRect(GetTextLength(), true);
  return true;
}

bool CFDE_TxtEdtEngine::IsFitArea(CFX_WideString& wsText) {
  auto pTextOut = pdfium::MakeUnique<CFDE_TextOut>();
  pTextOut->SetLineSpace(m_Param.fLineSpace);
  pTextOut->SetFont(m_Param.pFont);
  pTextOut->SetFontSize(m_Param.fFontSize);

  FDE_TextStyle dwStyle;
  if (!(m_Param.dwMode & FDE_TEXTEDITMODE_MultiLines))
    dwStyle.single_line_ = true;

  CFX_RectF rcText;
  if (m_Param.dwMode & FDE_TEXTEDITMODE_AutoLineWrap) {
    dwStyle.line_wrap_ = true;
    rcText.width = m_Param.fPlateWidth;
  } else {
    rcText.width = 65535;
  }
  pTextOut->SetStyles(dwStyle);
  wsText += L"\n";
  pTextOut->CalcLogicSize(wsText.c_str(), wsText.GetLength(), rcText);
  wsText.Delete(wsText.GetLength() - 1);
  if ((m_Param.dwMode & FDE_TEXTEDITMODE_LimitArea_Horz) &&
      (rcText.width > m_Param.fPlateWidth)) {
    return false;
  }
  if ((m_Param.dwMode & FDE_TEXTEDITMODE_LimitArea_Vert) &&
      (rcText.height > m_Param.fLineSpace * m_Param.nLineCount)) {
    return false;
  }
  return true;
}

void CFDE_TxtEdtEngine::UpdateCaretRect(int32_t nIndex, bool bBefore) {
  MovePage2Char(nIndex);
  GetCaretRect(m_rtCaret, m_nCaretPage, nIndex, bBefore);
  m_nCaret = nIndex;
  m_bBefore = bBefore;
  if (!m_bBefore) {
    m_nCaret++;
    m_bBefore = true;
  }
  m_fCaretPosReserve = m_rtCaret.left;
  m_Param.pEventSink->OnCaretChanged();
}

void CFDE_TxtEdtEngine::GetCaretRect(CFX_RectF& rtCaret,
                                     int32_t nPageIndex,
                                     int32_t nCaret,
                                     bool bBefore) {
  CFDE_TxtEdtPage* pPage = m_PagePtrArray[m_nCaretPage].get();
  m_Param.pEventSink->OnPageLoad(m_nCaretPage);

  bool bCombText = !!(m_Param.dwLayoutStyles & FDE_TEXTEDITLAYOUT_CombText);
  int32_t nIndexInpage = nCaret - pPage->GetCharStart();
  if (bBefore && bCombText && nIndexInpage > 0) {
    nIndexInpage--;
    bBefore = false;
  }
  int32_t nBIDILevel = pPage->GetCharRect(nIndexInpage, rtCaret, bCombText);
  if ((!FX_IsOdd(nBIDILevel) && !bBefore) ||
      (FX_IsOdd(nBIDILevel) && bBefore)) {
    rtCaret.Offset(rtCaret.width - 1.0f, 0);
  }
  if (rtCaret.width == 0 && rtCaret.left > 1.0f)
    rtCaret.left -= 1.0f;

  rtCaret.width = 1.0f;

  m_Param.pEventSink->OnPageUnload(m_nCaretPage);
}

void CFDE_TxtEdtEngine::UpdateCaretIndex(const CFX_PointF& ptCaret) {
  CFDE_TxtEdtPage* pPage = m_PagePtrArray[m_nCaretPage].get();
  m_Param.pEventSink->OnPageLoad(m_nCaretPage);
  m_nCaret = pPage->GetCharIndex(ptCaret, m_bBefore);
  GetCaretRect(m_rtCaret, m_nCaretPage, m_nCaret, m_bBefore);
  if (!m_bBefore) {
    m_nCaret++;
    m_bBefore = true;
  }
  m_Param.pEventSink->OnCaretChanged();
  m_Param.pEventSink->OnPageUnload(m_nCaretPage);
}

void CFDE_TxtEdtEngine::DeleteSelect() {
  int32_t nCountRange = CountSelRanges();
  if (nCountRange <= 0)
    return;

  int32_t nSelStart = 0;
  while (nCountRange > 0) {
    int32_t nSelCount = GetSelRange(--nCountRange, &nSelStart);
    m_SelRangePtrArr.erase(m_SelRangePtrArr.begin() + nCountRange);
    DeleteRange_DoRecord(nSelStart, nSelCount, true);
  }
  ClearSelection();
  m_Param.pEventSink->OnTextChanged(m_ChangeInfo);
  m_Param.pEventSink->OnSelChanged();
  SetCaretPos(nSelStart, true);
}