HRESULT touchmind::control::DWriteEditControlTextStoreACP::Delete() {
  if (_IsLocked(TS_LF_READ)) {
    return S_OK;
  }
  _LockDocument(TS_LF_READWRITE);

  bool changed = false;
  TS_TEXTCHANGE ts;
  if (m_acpStart == m_acpEnd) {
    if (m_acpStart < (LONG)m_text.length()) {
      _Internal_RemoveText(m_acpStart, 1);
      ts.acpStart = m_acpStart;
      ts.acpOldEnd = m_acpStart + 1;
      ts.acpNewEnd = m_acpStart;
      changed = true;
    }
  } else {
    _Internal_RemoveText(m_acpStart, m_acpEnd - m_acpStart);
    ts.acpStart = m_acpStart;
    ts.acpOldEnd = m_acpEnd;
    ts.acpNewEnd = m_acpStart;
    changed = true;
  }

  if (changed) {
    m_AdviseSink.pTextStoreACPSink->OnTextChange(0, &ts);
  }

  _UnlockDocument();

  m_AdviseSink.pTextStoreACPSink->OnLayoutChange(TS_LC_CHANGE, EDIT_VIEW_COOKIE);
  return S_OK;
}
STDMETHODIMP touchmind::control::DWriteEditControlTextStoreACP::SetSelection(ULONG ulCount,
                                                                             const TS_SELECTION_ACP *pSelection) {
  if (nullptr == pSelection) {
    return E_INVALIDARG;
  }

  if (ulCount > 1) {
    return E_INVALIDARG;
  }

  if (!_IsLocked(TS_LF_READWRITE)) {
    return TS_E_NOLOCK;
  }

  m_acpStart = pSelection[0].acpStart;
  m_acpEnd = pSelection[0].acpEnd;
  m_fInterimChar = pSelection[0].style.fInterimChar;
  if (m_fInterimChar) {
    m_ActiveSelEnd = TS_AE_NONE;
  } else {
    m_ActiveSelEnd = pSelection[0].style.ase;
  }

  LONG lStart = m_acpStart;
  LONG lEnd = m_acpEnd;

  if (TS_AE_START == m_ActiveSelEnd) {
    lStart = m_acpEnd;
    lEnd = m_acpStart;
  }

  m_fNotify = TRUE;

  return S_OK;
}
HRESULT touchmind::control::DWriteEditControlTextStoreACP::InsertTextAtACP(std::wstring &text) {
  if (_IsLocked(TS_LF_READ)) {
    return S_OK;
  }
  _LockDocument(TS_LF_READWRITE);

  bool changed = false;
  TS_TEXTCHANGE ts;
  if (m_acpStart == m_acpEnd) {
    _Internal_InsertText(m_acpStart, text);
    ts.acpStart = m_acpStart;
    ts.acpOldEnd = m_acpStart;
    ts.acpNewEnd = m_acpStart + static_cast<LONG>(text.length());
    changed = true;
    m_acpStart = m_acpEnd = m_acpStart + 1;
  } else {
    _Internal_ReplaceText(m_acpStart, m_acpEnd - m_acpStart, text);
    ts.acpStart = m_acpStart;
    ts.acpOldEnd = m_acpEnd;
    ts.acpNewEnd = m_acpStart + static_cast<LONG>(text.length());
    changed = true;
    m_acpStart = m_acpEnd = m_acpStart + static_cast<LONG>(text.length());
  }

  if (changed) {
    m_AdviseSink.pTextStoreACPSink->OnTextChange(0, &ts);
  }

  _UnlockDocument();

  m_AdviseSink.pTextStoreACPSink->OnLayoutChange(TS_LC_CHANGE, EDIT_VIEW_COOKIE);
  return S_OK;
}
STDMETHODIMP touchmind::control::DWriteEditControlTextStoreACP::GetTextExt(TsViewCookie vcView, LONG acpStart,
                                                                           LONG acpEnd, RECT *prc, BOOL *pfClipped) {
  if (nullptr == prc || nullptr == pfClipped) {
    return E_INVALIDARG;
  }

  *pfClipped = FALSE;
  ZeroMemory(prc, sizeof(RECT));

  if (EDIT_VIEW_COOKIE != vcView) {
    return E_INVALIDARG;
  }

  if (!_IsLocked(TS_LF_READ)) {
    return TS_E_NOLOCK;
  }

  UINT32 actualCount;
  std::shared_ptr<control::DWriteEditControl> editControl = m_pEditControl.lock();
  if (editControl->GetTextLayout()->HitTestTextRange(acpStart, acpEnd - acpStart + 1, editControl->GetLayoutRect().left,
                                                     editControl->GetLayoutRect().top, nullptr, 0, &actualCount)
      != E_NOT_SUFFICIENT_BUFFER) {
    return E_INVALIDARG;
  }

  std::vector<DWRITE_HIT_TEST_METRICS> hitTestMetrics(actualCount);
  editControl->GetTextLayout()->HitTestTextRange(acpStart, acpEnd - acpStart + 1, editControl->GetLayoutRect().left,
                                                 editControl->GetLayoutRect().top, hitTestMetrics.data(), actualCount,
                                                 &actualCount);

  if (actualCount > 0) {
    D2D1_POINT_2F p1;
    p1.x = hitTestMetrics[0].left;
    p1.y = hitTestMetrics[0].top;
    D2D1_POINT_2F p2;
    p2.x = (hitTestMetrics[0].left + hitTestMetrics[0].width);
    p2.y = (hitTestMetrics[0].top + hitTestMetrics[0].height);

    POINT pp1;
    touchmind::util::CoordinateConversion::ConvertModelToWindowCoordinate(m_hWnd, editControl->GetScrollBarHelper(), p1,
                                                                          &pp1);
    POINT pp2;
    touchmind::util::CoordinateConversion::ConvertModelToWindowCoordinate(m_hWnd, editControl->GetScrollBarHelper(), p2,
                                                                          &pp2);

    ClientToScreen(m_hWnd, &pp1);
    ClientToScreen(m_hWnd, &pp2);

    prc->left = pp1.x;
    prc->top = pp1.y;
    prc->right = pp2.x;
    prc->bottom = pp2.y;
    pfClipped = FALSE;
  }

  return S_OK;
}
HRESULT touchmind::control::DWriteEditControlTextStoreACP::MoveACPToEnd() {
  if (_IsLocked(TS_LF_READ)) {
    return S_OK;
  }
  _LockDocument(TS_LF_READWRITE);
  LONG textLength = static_cast<LONG>(GetTextLength());
  HRESULT hr = ChangeACPWithoutLock(textLength, textLength, TS_AE_START);
  _UnlockDocument();
  m_AdviseSink.pTextStoreACPSink->OnLayoutChange(TS_LC_CHANGE, EDIT_VIEW_COOKIE);
  return hr;
}
STDMETHODIMP touchmind::control::DWriteEditControlTextStoreACP::GetEndACP(LONG *pacp) {
  if (!_IsLocked(TS_LF_READWRITE)) {
    return TS_E_NOLOCK;
  }

  if (nullptr == pacp) {
    return E_INVALIDARG;
  }

  *pacp = static_cast<LONG>(m_text.length());
  return S_OK;
}
STDMETHODIMP touchmind::control::DWriteEditControlTextStoreACP::GetSelection(ULONG ulIndex, ULONG ulCount,
                                                                             TS_SELECTION_ACP *pSelection,
                                                                             ULONG *pcFetched) {
  UNREFERENCED_PARAMETER(ulCount);

  if (nullptr == pSelection) {
    return E_INVALIDARG;
  }

  if (nullptr == pcFetched) {
    return E_INVALIDARG;
  }

  *pcFetched = 0;

  if (!_IsLocked(TS_LF_READ)) {
    return TS_E_NOLOCK;
  }

  if (TF_DEFAULT_SELECTION == ulIndex) {
    ulIndex = 0;
  } else if (ulIndex > 1) {
    return E_INVALIDARG;
  }

  pSelection[0].acpStart = m_acpStart;
  pSelection[0].acpEnd = m_acpEnd;
  pSelection[0].style.fInterimChar = m_fInterimChar;
  if (m_fInterimChar) {
    pSelection[0].style.ase = TS_AE_NONE;
  } else {
    pSelection[0].style.ase = m_ActiveSelEnd;
  }
  *pcFetched = 1;

  return S_OK;
}
STDMETHODIMP
touchmind::control::DWriteEditControlTextStoreACP::InsertTextAtSelection(DWORD dwFlags,
                                                                         const WCHAR *pwszText, // null if cch is 0
                                                                         ULONG cch, LONG *pacpStart, LONG *pacpEnd,
                                                                         TS_TEXTCHANGE *pChange) {
  LONG lTemp;

  if (!_IsLocked(TS_LF_READWRITE)) {
    return TS_E_NOLOCK;
  }

  if (cch > 0L && nullptr == pwszText) {
    return E_POINTER;
  }

  if (nullptr == pacpStart) {
    pacpStart = &lTemp;
  }

  if (nullptr == pacpEnd) {
    pacpEnd = &lTemp;
  }

  LONG acpStart;
  LONG acpOldEnd;
  LONG acpNewEnd;

  _GetCurrentSelection();

  acpOldEnd = m_acpEnd;
  HRESULT hr = _TestInsert(m_acpStart, m_acpEnd, cch, &acpStart, &acpNewEnd);
  if (FAILED(hr)) {
    return hr;
  }

  if (dwFlags & TS_IAS_QUERYONLY) {
    *pacpStart = acpStart;
    *pacpEnd = acpNewEnd;
    return S_OK;
  }

  m_fNotify = FALSE;

  std::wstring s(pwszText, cch);

  if ((acpOldEnd - acpStart > 0) && (acpNewEnd - acpStart > 0)) {
    _Internal_ReplaceText(acpStart, acpOldEnd - acpStart, s);
  } else if (acpOldEnd - acpStart > 0) {
    _Internal_RemoveText(acpStart, acpOldEnd - acpStart);
  } else if (acpNewEnd - acpStart > 0) {
    _Internal_InsertText(acpStart, s);
  }

  m_fNotify = TRUE;

  _GetCurrentSelection();

  if (!(dwFlags & TS_IAS_NOQUERY)) {
    *pacpStart = acpStart;
    *pacpEnd = acpNewEnd;
  }

  pChange->acpStart = acpStart;
  pChange->acpOldEnd = acpOldEnd;
  pChange->acpNewEnd = acpNewEnd;

  m_fLayoutChanged = TRUE;

  Invalidate();
  NotifySelectionHasChanged();
  return S_OK;
}
STDMETHODIMP touchmind::control::DWriteEditControlTextStoreACP::GetText(
    IN LONG acpStart, IN LONG acpEnd, OUT WCHAR *pchPlain, IN ULONG cchPlainReq, OUT ULONG *pcchPlainRet,
    OUT TS_RUNINFO *prgRunInfo, IN ULONG ulRunInfoReq, OUT ULONG *pulRunInfoOut, OUT LONG *pacpNext) {
  if (!_IsLocked(TS_LF_READ)) {
    return TS_E_NOLOCK;
  }

  BOOL fDoText = cchPlainReq > 0;
  BOOL fDoRunInfo = ulRunInfoReq > 0;
  LONG cchTotal;
  HRESULT hr = E_FAIL;

  if (pcchPlainRet) {
    *pcchPlainRet = 0;
  }

  if (fDoRunInfo) {
    *pulRunInfoOut = 0;
  }

  if (pacpNext) {
    *pacpNext = acpStart;
  }

  std::wstring text;
  _GetText(text, &cchTotal);

  if ((acpStart < 0) || (acpStart > cchTotal)) {
    hr = TS_E_INVALIDPOS;
  } else {
    if (acpStart == cchTotal) {
      hr = S_OK;
    } else {
      ULONG cchReq;

      if (acpEnd >= acpStart) {
        cchReq = acpEnd - acpStart;
      } else {
        cchReq = cchTotal - acpStart;
      }

      if (fDoText) {
        if (cchReq > cchPlainReq) {
          cchReq = cchPlainReq;
        }

        std::wstring substr = text.substr(acpStart, cchReq);

        if (pchPlain && cchPlainReq) {
          CopyMemory(pchPlain, substr.c_str(), cchReq * sizeof(WCHAR));
        }
      }

      if (pcchPlainRet) {
        *pcchPlainRet = cchReq;
      }

      if (fDoRunInfo) {
        *pulRunInfoOut = 1;
        prgRunInfo[0].type = TS_RT_PLAIN;
        prgRunInfo[0].uCount = cchReq;
      }

      if (pacpNext) {
        *pacpNext = acpStart + cchReq;
      }

      hr = S_OK;
    }
  }
  return hr;
}
void CTSFEditWnd::_Load(IStream *pStream)
{
    if(NULL == pStream)
    {
        return;
    }

    //can't do this if someone has a lock
    if(_IsLocked(TS_LF_READ))
    {
        return;
    }

    _ClearText();

    HRESULT         hr;
    ULONG           uRead;
    LARGE_INTEGER   li;
    ULONG           uSize;

    //set the stream pointer to the start of the stream
    li.QuadPart = 0;
    pStream->Seek(li, STREAM_SEEK_SET, NULL);

    //get the size of the text, in BYTES. This is the first ULONG in the stream
    hr = pStream->Read(&uSize, sizeof(ULONG), &uRead);
    if(SUCCEEDED(hr) && (sizeof(ULONG) == uRead))
    {
        LPWSTR  pwsz;
        
        //allocate a buffer for the text plus one NULL character
        pwsz = (LPWSTR)GlobalAlloc(GPTR, uSize + sizeof(WCHAR));
        if(NULL != pwsz)
        {
            //get the plain UNICODE text from the stream
            hr = pStream->Read(pwsz, uSize, &uRead);
            if(SUCCEEDED(hr) && (uSize == uRead))
            {
                TF_PERSISTENT_PROPERTY_HEADER_ACP   PropHeader;
                
                //put the text into the edit control, but don't send a change notification
                BOOL    fOldNotify = m_fNotify;
                m_fNotify = FALSE;
                SetWindowTextW(m_hwndEdit, pwsz);
                m_fNotify = fOldNotify;

                /*
                Read each property header and property data from the stream. The 
                list of properties is terminated by a TF_PERSISTENT_PROPERTY_HEADER_ACP 
                structure with a cb member of zero.
                */
                hr = pStream->Read(&PropHeader, sizeof(TF_PERSISTENT_PROPERTY_HEADER_ACP), &uRead);
                while(  SUCCEEDED(hr) && 
                        (sizeof(TF_PERSISTENT_PROPERTY_HEADER_ACP) == uRead) && 
                        (0 != PropHeader.cb))
                {
                    ITfProperty *pProp;

                    hr = m_pContext->GetProperty(PropHeader.guidType, &pProp);
                    if(SUCCEEDED(hr))
                    {
                        /*
                        Have TSF read the property data from the stream. This call 
                        will request a read lock, so make sure it can be granted 
                        or else this method will fail.
                        */
                        CTSFPersistentPropertyLoader *pLoader = new CTSFPersistentPropertyLoader(&PropHeader, pStream);
                        hr = m_pServices->Unserialize(pProp, &PropHeader, NULL, pLoader);

                        pProp->Release();
                    }

                    hr = pStream->Read(&PropHeader, sizeof(TF_PERSISTENT_PROPERTY_HEADER_ACP), &uRead);
                }
            }
            
            GlobalFree(pwsz);
        }
    }
}