| // 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_textout.h" | 
 |  | 
 | #include <algorithm> | 
 | #include <utility> | 
 |  | 
 | #include "core/fxcrt/fx_coordinates.h" | 
 | #include "core/fxcrt/fx_system.h" | 
 | #include "core/fxge/cfx_font.h" | 
 | #include "core/fxge/cfx_pathdata.h" | 
 | #include "third_party/base/ptr_util.h" | 
 | #include "third_party/base/stl_util.h" | 
 | #include "xfa/fgas/layout/cfx_txtbreak.h" | 
 |  | 
 | namespace { | 
 |  | 
 | bool TextAlignmentVerticallyCentered(const FDE_TextAlignment align) { | 
 |   return align == FDE_TextAlignment::kCenterLeft || | 
 |          align == FDE_TextAlignment::kCenter || | 
 |          align == FDE_TextAlignment::kCenterRight; | 
 | } | 
 |  | 
 | bool IsTextAlignmentTop(const FDE_TextAlignment align) { | 
 |   return align == FDE_TextAlignment::kTopLeft; | 
 | } | 
 |  | 
 | }  // namespace | 
 |  | 
 | // static | 
 | bool CFDE_TextOut::DrawString(CFX_RenderDevice* device, | 
 |                               FX_ARGB color, | 
 |                               const RetainPtr<CFGAS_GEFont>& pFont, | 
 |                               FXTEXT_CHARPOS* pCharPos, | 
 |                               int32_t iCount, | 
 |                               float fFontSize, | 
 |                               const CFX_Matrix* pMatrix) { | 
 |   ASSERT(pFont && pCharPos && iCount > 0); | 
 |  | 
 |   CFX_Font* pFxFont = pFont->GetDevFont(); | 
 |   if (FontStyleIsItalic(pFont->GetFontStyles()) && !pFxFont->IsItalic()) { | 
 |     for (int32_t i = 0; i < iCount; ++i) { | 
 |       static const float mc = 0.267949f; | 
 |       float* pAM = pCharPos->m_AdjustMatrix; | 
 |       pAM[2] = mc * pAM[0] + pAM[2]; | 
 |       pAM[3] = mc * pAM[1] + pAM[3]; | 
 |       ++pCharPos; | 
 |     } | 
 |   } | 
 |  | 
 | #if _FX_PLATFORM_ != _FX_PLATFORM_WINDOWS_ | 
 |   uint32_t dwFontStyle = pFont->GetFontStyles(); | 
 |   CFX_Font FxFont; | 
 |   auto SubstFxFont = pdfium::MakeUnique<CFX_SubstFont>(); | 
 |   SubstFxFont->m_Weight = FontStyleIsBold(dwFontStyle) ? 700 : 400; | 
 |   SubstFxFont->m_ItalicAngle = FontStyleIsItalic(dwFontStyle) ? -12 : 0; | 
 |   SubstFxFont->m_WeightCJK = SubstFxFont->m_Weight; | 
 |   SubstFxFont->m_bItalicCJK = FontStyleIsItalic(dwFontStyle); | 
 |   FxFont.SetSubstFont(std::move(SubstFxFont)); | 
 | #endif  // _FX_PLATFORM_ != _FX_PLATFORM_WINDOWS_ | 
 |  | 
 |   RetainPtr<CFGAS_GEFont> pCurFont; | 
 |   FXTEXT_CHARPOS* pCurCP = nullptr; | 
 |   int32_t iCurCount = 0; | 
 |   for (int32_t i = 0; i < iCount; ++i) { | 
 |     RetainPtr<CFGAS_GEFont> pSTFont = | 
 |         pFont->GetSubstFont(static_cast<int32_t>(pCharPos->m_GlyphIndex)); | 
 |     pCharPos->m_GlyphIndex &= 0x00FFFFFF; | 
 |     pCharPos->m_bFontStyle = false; | 
 |     if (pCurFont != pSTFont) { | 
 |       if (pCurFont) { | 
 |         pFxFont = pCurFont->GetDevFont(); | 
 |  | 
 |         CFX_Font* font; | 
 | #if _FX_PLATFORM_ != _FX_PLATFORM_WINDOWS_ | 
 |         FxFont.SetFace(pFxFont->GetFace()); | 
 |         font = &FxFont; | 
 | #else | 
 |         font = pFxFont; | 
 | #endif  // _FX_PLATFORM_ != _FX_PLATFORM_WINDOWS_ | 
 |  | 
 |         device->DrawNormalText(iCurCount, pCurCP, font, -fFontSize, pMatrix, | 
 |                                color, FXTEXT_CLEARTYPE); | 
 |       } | 
 |       pCurFont = pSTFont; | 
 |       pCurCP = pCharPos; | 
 |       iCurCount = 1; | 
 |     } else { | 
 |       ++iCurCount; | 
 |     } | 
 |     ++pCharPos; | 
 |   } | 
 |  | 
 |   bool bRet = true; | 
 |   if (pCurFont && iCurCount) { | 
 |     pFxFont = pCurFont->GetDevFont(); | 
 |     CFX_Font* font; | 
 | #if _FX_PLATFORM_ != _FX_PLATFORM_WINDOWS_ | 
 |     FxFont.SetFace(pFxFont->GetFace()); | 
 |     font = &FxFont; | 
 | #else | 
 |     font = pFxFont; | 
 | #endif  // _FX_PLATFORM_ != _FX_PLATFORM_WINDOWS_ | 
 |  | 
 |     bRet = device->DrawNormalText(iCurCount, pCurCP, font, -fFontSize, pMatrix, | 
 |                                   color, FXTEXT_CLEARTYPE); | 
 |   } | 
 |  | 
 | #if _FX_PLATFORM_ != _FX_PLATFORM_WINDOWS_ | 
 |   FxFont.SetFace(nullptr); | 
 | #endif  // _FX_PLATFORM_ != _FX_PLATFORM_WINDOWS_ | 
 |  | 
 |   return bRet; | 
 | } | 
 |  | 
 | FDE_TTOPIECE::FDE_TTOPIECE() = default; | 
 |  | 
 | FDE_TTOPIECE::FDE_TTOPIECE(const FDE_TTOPIECE& that) = default; | 
 |  | 
 | FDE_TTOPIECE::~FDE_TTOPIECE() = default; | 
 |  | 
 | CFDE_TextOut::CFDE_TextOut() | 
 |     : m_pTxtBreak(pdfium::MakeUnique<CFX_TxtBreak>()), | 
 |       m_pFont(nullptr), | 
 |       m_fFontSize(12.0f), | 
 |       m_fLineSpace(m_fFontSize), | 
 |       m_fLinePos(0.0f), | 
 |       m_fTolerance(0.0f), | 
 |       m_iAlignment(FDE_TextAlignment::kTopLeft), | 
 |       m_TxtColor(0xFF000000), | 
 |       m_dwTxtBkStyles(0), | 
 |       m_ttoLines(5), | 
 |       m_iCurLine(0), | 
 |       m_iCurPiece(0), | 
 |       m_iTotalLines(0) {} | 
 |  | 
 | CFDE_TextOut::~CFDE_TextOut() {} | 
 |  | 
 | void CFDE_TextOut::SetFont(const RetainPtr<CFGAS_GEFont>& pFont) { | 
 |   ASSERT(pFont); | 
 |   m_pFont = pFont; | 
 |   m_pTxtBreak->SetFont(pFont); | 
 | } | 
 |  | 
 | void CFDE_TextOut::SetFontSize(float fFontSize) { | 
 |   ASSERT(fFontSize > 0); | 
 |   m_fFontSize = fFontSize; | 
 |   m_pTxtBreak->SetFontSize(fFontSize); | 
 | } | 
 |  | 
 | void CFDE_TextOut::SetStyles(const FDE_TextStyle& dwStyles) { | 
 |   m_Styles = dwStyles; | 
 |  | 
 |   m_dwTxtBkStyles = 0; | 
 |   if (m_Styles.single_line_) | 
 |     m_dwTxtBkStyles |= FX_LAYOUTSTYLE_SingleLine; | 
 |  | 
 |   m_pTxtBreak->SetLayoutStyles(m_dwTxtBkStyles); | 
 | } | 
 |  | 
 | void CFDE_TextOut::SetAlignment(FDE_TextAlignment iAlignment) { | 
 |   m_iAlignment = iAlignment; | 
 |  | 
 |   int32_t txtBreakAlignment = 0; | 
 |   switch (m_iAlignment) { | 
 |     case FDE_TextAlignment::kCenter: | 
 |       txtBreakAlignment = CFX_TxtLineAlignment_Center; | 
 |       break; | 
 |     case FDE_TextAlignment::kCenterRight: | 
 |       txtBreakAlignment = CFX_TxtLineAlignment_Right; | 
 |       break; | 
 |     case FDE_TextAlignment::kCenterLeft: | 
 |     case FDE_TextAlignment::kTopLeft: | 
 |       txtBreakAlignment = CFX_TxtLineAlignment_Left; | 
 |       break; | 
 |   } | 
 |   m_pTxtBreak->SetAlignment(txtBreakAlignment); | 
 | } | 
 |  | 
 | void CFDE_TextOut::SetLineSpace(float fLineSpace) { | 
 |   ASSERT(fLineSpace > 1.0f); | 
 |   m_fLineSpace = fLineSpace; | 
 | } | 
 |  | 
 | void CFDE_TextOut::SetLineBreakTolerance(float fTolerance) { | 
 |   m_fTolerance = fTolerance; | 
 |   m_pTxtBreak->SetLineBreakTolerance(m_fTolerance); | 
 | } | 
 |  | 
 | void CFDE_TextOut::CalcLogicSize(const WideString& str, CFX_SizeF& size) { | 
 |   CFX_RectF rtText(0.0f, 0.0f, size.width, size.height); | 
 |   CalcLogicSize(str, rtText); | 
 |   size = rtText.Size(); | 
 | } | 
 |  | 
 | void CFDE_TextOut::CalcLogicSize(const WideString& str, CFX_RectF& rect) { | 
 |   if (str.IsEmpty()) { | 
 |     rect.width = 0.0f; | 
 |     rect.height = 0.0f; | 
 |     return; | 
 |   } | 
 |  | 
 |   ASSERT(m_pFont && m_fFontSize >= 1.0f); | 
 |  | 
 |   if (!m_Styles.single_line_) { | 
 |     if (rect.Width() < 1.0f) | 
 |       rect.width = m_fFontSize * 1000.0f; | 
 |  | 
 |     m_pTxtBreak->SetLineWidth(rect.Width()); | 
 |   } | 
 |  | 
 |   m_iTotalLines = 0; | 
 |   float fWidth = 0.0f; | 
 |   float fHeight = 0.0f; | 
 |   float fStartPos = rect.right(); | 
 |   CFX_BreakType dwBreakStatus = CFX_BreakType::None; | 
 |   bool break_char_is_set = false; | 
 |   for (const wchar_t& wch : str) { | 
 |     if (!break_char_is_set && (wch == L'\n' || wch == L'\r')) { | 
 |       break_char_is_set = true; | 
 |       m_pTxtBreak->SetParagraphBreakChar(wch); | 
 |     } | 
 |     dwBreakStatus = m_pTxtBreak->AppendChar(wch); | 
 |     if (!CFX_BreakTypeNoneOrPiece(dwBreakStatus)) | 
 |       RetrieveLineWidth(dwBreakStatus, fStartPos, fWidth, fHeight); | 
 |   } | 
 |  | 
 |   dwBreakStatus = m_pTxtBreak->EndBreak(CFX_BreakType::Paragraph); | 
 |   if (!CFX_BreakTypeNoneOrPiece(dwBreakStatus)) | 
 |     RetrieveLineWidth(dwBreakStatus, fStartPos, fWidth, fHeight); | 
 |  | 
 |   m_pTxtBreak->Reset(); | 
 |   float fInc = rect.Height() - fHeight; | 
 |   if (TextAlignmentVerticallyCentered(m_iAlignment)) | 
 |     fInc /= 2.0f; | 
 |   else if (IsTextAlignmentTop(m_iAlignment)) | 
 |     fInc = 0.0f; | 
 |  | 
 |   rect.left += fStartPos; | 
 |   rect.top += fInc; | 
 |   rect.width = std::min(fWidth, rect.Width()); | 
 |   rect.height = fHeight; | 
 |   if (m_Styles.last_line_height_) | 
 |     rect.height -= m_fLineSpace - m_fFontSize; | 
 | } | 
 |  | 
 | bool CFDE_TextOut::RetrieveLineWidth(CFX_BreakType dwBreakStatus, | 
 |                                      float& fStartPos, | 
 |                                      float& fWidth, | 
 |                                      float& fHeight) { | 
 |   if (CFX_BreakTypeNoneOrPiece(dwBreakStatus)) | 
 |     return false; | 
 |  | 
 |   float fLineStep = (m_fLineSpace > m_fFontSize) ? m_fLineSpace : m_fFontSize; | 
 |   float fLineWidth = 0.0f; | 
 |   for (int32_t i = 0; i < m_pTxtBreak->CountBreakPieces(); i++) { | 
 |     const CFX_BreakPiece* pPiece = m_pTxtBreak->GetBreakPieceUnstable(i); | 
 |     fLineWidth += static_cast<float>(pPiece->m_iWidth) / 20000.0f; | 
 |     fStartPos = | 
 |         std::min(fStartPos, static_cast<float>(pPiece->m_iStartPos) / 20000.0f); | 
 |   } | 
 |   m_pTxtBreak->ClearBreakPieces(); | 
 |  | 
 |   if (dwBreakStatus == CFX_BreakType::Paragraph) | 
 |     m_pTxtBreak->Reset(); | 
 |   if (!m_Styles.line_wrap_ && dwBreakStatus == CFX_BreakType::Line) { | 
 |     fWidth += fLineWidth; | 
 |   } else { | 
 |     fWidth = std::max(fWidth, fLineWidth); | 
 |     fHeight += fLineStep; | 
 |   } | 
 |   ++m_iTotalLines; | 
 |   return true; | 
 | } | 
 |  | 
 | void CFDE_TextOut::DrawLogicText(CFX_RenderDevice* device, | 
 |                                  const WideStringView& str, | 
 |                                  const CFX_RectF& rect) { | 
 |   ASSERT(m_pFont && m_fFontSize >= 1.0f); | 
 |  | 
 |   if (str.IsEmpty()) | 
 |     return; | 
 |   if (rect.width < m_fFontSize || rect.height < m_fFontSize) | 
 |     return; | 
 |  | 
 |   float fLineWidth = rect.width; | 
 |   m_pTxtBreak->SetLineWidth(fLineWidth); | 
 |   m_ttoLines.clear(); | 
 |   m_wsText.clear(); | 
 |  | 
 |   LoadText(WideString(str), rect); | 
 |   Reload(rect); | 
 |   DoAlignment(rect); | 
 |  | 
 |   if (!device || m_ttoLines.empty()) | 
 |     return; | 
 |  | 
 |   CFX_RectF rtClip = m_Matrix.TransformRect(CFX_RectF()); | 
 |   device->SaveState(); | 
 |   if (rtClip.Width() > 0.0f && rtClip.Height() > 0.0f) | 
 |     device->SetClip_Rect(rtClip); | 
 |  | 
 |   for (auto& line : m_ttoLines) { | 
 |     int32_t iPieces = line.GetSize(); | 
 |     for (int32_t j = 0; j < iPieces; j++) { | 
 |       FDE_TTOPIECE* pPiece = line.GetPtrAt(j); | 
 |       if (!pPiece) | 
 |         continue; | 
 |  | 
 |       int32_t iCount = GetDisplayPos(pPiece); | 
 |       if (iCount > 0) { | 
 |         CFDE_TextOut::DrawString(device, m_TxtColor, m_pFont, m_CharPos.data(), | 
 |                                  iCount, m_fFontSize, &m_Matrix); | 
 |       } | 
 |     } | 
 |   } | 
 |   device->RestoreState(false); | 
 | } | 
 |  | 
 | void CFDE_TextOut::LoadText(const WideString& str, const CFX_RectF& rect) { | 
 |   ASSERT(!str.IsEmpty()); | 
 |  | 
 |   m_wsText = str; | 
 |  | 
 |   if (pdfium::CollectionSize<size_t>(m_CharWidths) < str.GetLength()) | 
 |     m_CharWidths.resize(str.GetLength(), 0); | 
 |  | 
 |   float fLineStep = (m_fLineSpace > m_fFontSize) ? m_fLineSpace : m_fFontSize; | 
 |   float fLineStop = rect.bottom(); | 
 |   m_fLinePos = rect.top; | 
 |   int32_t iStartChar = 0; | 
 |   int32_t iPieceWidths = 0; | 
 |   CFX_BreakType dwBreakStatus; | 
 |   bool bRet = false; | 
 |   for (const auto& wch : str) { | 
 |     dwBreakStatus = m_pTxtBreak->AppendChar(wch); | 
 |     if (CFX_BreakTypeNoneOrPiece(dwBreakStatus)) | 
 |       continue; | 
 |  | 
 |     bool bEndofLine = | 
 |         RetrievePieces(dwBreakStatus, iStartChar, iPieceWidths, false, rect); | 
 |     if (bEndofLine && | 
 |         (m_Styles.line_wrap_ || dwBreakStatus == CFX_BreakType::Paragraph || | 
 |          dwBreakStatus == CFX_BreakType::Page)) { | 
 |       iPieceWidths = 0; | 
 |       ++m_iCurLine; | 
 |       m_fLinePos += fLineStep; | 
 |     } | 
 |     if (m_fLinePos + fLineStep > fLineStop) { | 
 |       int32_t iCurLine = bEndofLine ? m_iCurLine - 1 : m_iCurLine; | 
 |       m_ttoLines[iCurLine].SetNewReload(true); | 
 |       bRet = true; | 
 |       break; | 
 |     } | 
 |   } | 
 |  | 
 |   dwBreakStatus = m_pTxtBreak->EndBreak(CFX_BreakType::Paragraph); | 
 |   if (!CFX_BreakTypeNoneOrPiece(dwBreakStatus) && !bRet) | 
 |     RetrievePieces(dwBreakStatus, iStartChar, iPieceWidths, false, rect); | 
 |  | 
 |   m_pTxtBreak->ClearBreakPieces(); | 
 |   m_pTxtBreak->Reset(); | 
 | } | 
 |  | 
 | bool CFDE_TextOut::RetrievePieces(CFX_BreakType dwBreakStatus, | 
 |                                   int32_t& iStartChar, | 
 |                                   int32_t& iPieceWidths, | 
 |                                   bool bReload, | 
 |                                   const CFX_RectF& rect) { | 
 |   float fLineStep = (m_fLineSpace > m_fFontSize) ? m_fLineSpace : m_fFontSize; | 
 |   bool bNeedReload = false; | 
 |   int32_t iLineWidth = FXSYS_round(rect.Width() * 20000.0f); | 
 |   int32_t iCount = m_pTxtBreak->CountBreakPieces(); | 
 |   for (int32_t i = 0; i < iCount; i++) { | 
 |     const CFX_BreakPiece* pPiece = m_pTxtBreak->GetBreakPieceUnstable(i); | 
 |     int32_t iPieceChars = pPiece->GetLength(); | 
 |     int32_t iChar = iStartChar; | 
 |     int32_t iWidth = 0; | 
 |     int32_t j = 0; | 
 |     for (; j < iPieceChars; j++) { | 
 |       const CFX_Char* pTC = pPiece->GetChar(j); | 
 |       int32_t iCurCharWidth = pTC->m_iCharWidth > 0 ? pTC->m_iCharWidth : 0; | 
 |       if (m_Styles.single_line_ || !m_Styles.line_wrap_) { | 
 |         if (iLineWidth - iPieceWidths - iWidth < iCurCharWidth) { | 
 |           bNeedReload = true; | 
 |           break; | 
 |         } | 
 |       } | 
 |       iWidth += iCurCharWidth; | 
 |       m_CharWidths[iChar++] = iCurCharWidth; | 
 |     } | 
 |  | 
 |     if (j == 0 && !bReload) { | 
 |       m_ttoLines[m_iCurLine].SetNewReload(true); | 
 |     } else if (j > 0) { | 
 |       FDE_TTOPIECE ttoPiece; | 
 |       ttoPiece.iStartChar = iStartChar; | 
 |       ttoPiece.iChars = j; | 
 |       ttoPiece.dwCharStyles = pPiece->m_dwCharStyles; | 
 |       ttoPiece.rtPiece = CFX_RectF( | 
 |           rect.left + static_cast<float>(pPiece->m_iStartPos) / 20000.0f, | 
 |           m_fLinePos, iWidth / 20000.0f, fLineStep); | 
 |  | 
 |       if (FX_IsOdd(pPiece->m_iBidiLevel)) | 
 |         ttoPiece.dwCharStyles |= FX_TXTCHARSTYLE_OddBidiLevel; | 
 |  | 
 |       AppendPiece(ttoPiece, bNeedReload, (bReload && i == iCount - 1)); | 
 |     } | 
 |     iStartChar += iPieceChars; | 
 |     iPieceWidths += iWidth; | 
 |   } | 
 |   m_pTxtBreak->ClearBreakPieces(); | 
 |  | 
 |   return m_Styles.single_line_ || m_Styles.line_wrap_ || bNeedReload || | 
 |          dwBreakStatus == CFX_BreakType::Paragraph; | 
 | } | 
 |  | 
 | void CFDE_TextOut::AppendPiece(const FDE_TTOPIECE& ttoPiece, | 
 |                                bool bNeedReload, | 
 |                                bool bEnd) { | 
 |   if (m_iCurLine >= pdfium::CollectionSize<int32_t>(m_ttoLines)) { | 
 |     CFDE_TTOLine ttoLine; | 
 |     ttoLine.SetNewReload(bNeedReload); | 
 |  | 
 |     m_iCurPiece = ttoLine.AddPiece(m_iCurPiece, ttoPiece); | 
 |     m_ttoLines.push_back(ttoLine); | 
 |     m_iCurLine = pdfium::CollectionSize<int32_t>(m_ttoLines) - 1; | 
 |   } else { | 
 |     CFDE_TTOLine* pLine = &m_ttoLines[m_iCurLine]; | 
 |     pLine->SetNewReload(bNeedReload); | 
 |  | 
 |     m_iCurPiece = pLine->AddPiece(m_iCurPiece, ttoPiece); | 
 |     if (bEnd) { | 
 |       int32_t iPieces = pLine->GetSize(); | 
 |       if (m_iCurPiece < iPieces) | 
 |         pLine->RemoveLast(iPieces - m_iCurPiece - 1); | 
 |     } | 
 |   } | 
 |   if (!bEnd && bNeedReload) | 
 |     m_iCurPiece = 0; | 
 | } | 
 |  | 
 | void CFDE_TextOut::Reload(const CFX_RectF& rect) { | 
 |   int i = 0; | 
 |   for (auto& line : m_ttoLines) { | 
 |     if (line.GetNewReload()) { | 
 |       m_iCurLine = i; | 
 |       m_iCurPiece = 0; | 
 |       ReloadLinePiece(&line, rect); | 
 |     } | 
 |     ++i; | 
 |   } | 
 | } | 
 |  | 
 | void CFDE_TextOut::ReloadLinePiece(CFDE_TTOLine* pLine, const CFX_RectF& rect) { | 
 |   const wchar_t* pwsStr = m_wsText.c_str(); | 
 |   int32_t iPieceWidths = 0; | 
 |  | 
 |   FDE_TTOPIECE* pPiece = pLine->GetPtrAt(0); | 
 |   int32_t iStartChar = pPiece->iStartChar; | 
 |   int32_t iPieceCount = pLine->GetSize(); | 
 |   int32_t iPieceIndex = 0; | 
 |   CFX_BreakType dwBreakStatus = CFX_BreakType::None; | 
 |   m_fLinePos = pPiece->rtPiece.top; | 
 |   while (iPieceIndex < iPieceCount) { | 
 |     int32_t iStar = iStartChar; | 
 |     int32_t iEnd = pPiece->iChars + iStar; | 
 |     while (iStar < iEnd) { | 
 |       dwBreakStatus = m_pTxtBreak->AppendChar(*(pwsStr + iStar)); | 
 |       if (!CFX_BreakTypeNoneOrPiece(dwBreakStatus)) | 
 |         RetrievePieces(dwBreakStatus, iStartChar, iPieceWidths, true, rect); | 
 |  | 
 |       ++iStar; | 
 |     } | 
 |     ++iPieceIndex; | 
 |     pPiece = pLine->GetPtrAt(iPieceIndex); | 
 |   } | 
 |  | 
 |   dwBreakStatus = m_pTxtBreak->EndBreak(CFX_BreakType::Paragraph); | 
 |   if (!CFX_BreakTypeNoneOrPiece(dwBreakStatus)) | 
 |     RetrievePieces(dwBreakStatus, iStartChar, iPieceWidths, true, rect); | 
 |  | 
 |   m_pTxtBreak->Reset(); | 
 | } | 
 |  | 
 | void CFDE_TextOut::DoAlignment(const CFX_RectF& rect) { | 
 |   if (m_ttoLines.empty()) | 
 |     return; | 
 |  | 
 |   FDE_TTOPIECE* pFirstPiece = m_ttoLines.back().GetPtrAt(0); | 
 |   if (!pFirstPiece) | 
 |     return; | 
 |  | 
 |   float fInc = rect.bottom() - pFirstPiece->rtPiece.bottom(); | 
 |   if (TextAlignmentVerticallyCentered(m_iAlignment)) | 
 |     fInc /= 2.0f; | 
 |   else if (IsTextAlignmentTop(m_iAlignment)) | 
 |     fInc = 0.0f; | 
 |  | 
 |   if (fInc < 1.0f) | 
 |     return; | 
 |  | 
 |   for (auto& line : m_ttoLines) { | 
 |     int32_t iPieces = line.GetSize(); | 
 |     for (int32_t j = 0; j < iPieces; j++) | 
 |       line.GetPtrAt(j)->rtPiece.top += fInc; | 
 |   } | 
 | } | 
 |  | 
 | int32_t CFDE_TextOut::GetDisplayPos(FDE_TTOPIECE* pPiece) { | 
 |   ASSERT(pPiece->iChars >= 0); | 
 |  | 
 |   if (pdfium::CollectionSize<int32_t>(m_CharPos) < pPiece->iChars) | 
 |     m_CharPos.resize(pPiece->iChars, FXTEXT_CHARPOS()); | 
 |  | 
 |   FX_TXTRUN tr; | 
 |   tr.wsStr = m_wsText + pPiece->iStartChar; | 
 |   tr.pWidths = &m_CharWidths[pPiece->iStartChar]; | 
 |   tr.iLength = pPiece->iChars; | 
 |   tr.pFont = m_pFont; | 
 |   tr.fFontSize = m_fFontSize; | 
 |   tr.dwStyles = m_dwTxtBkStyles; | 
 |   tr.dwCharStyles = pPiece->dwCharStyles; | 
 |   tr.pRect = &pPiece->rtPiece; | 
 |  | 
 |   return m_pTxtBreak->GetDisplayPos(&tr, m_CharPos.data()); | 
 | } | 
 |  | 
 | CFDE_TTOLine::CFDE_TTOLine() : m_bNewReload(false) {} | 
 |  | 
 | CFDE_TTOLine::CFDE_TTOLine(const CFDE_TTOLine& ttoLine) : m_pieces(5) { | 
 |   m_bNewReload = ttoLine.m_bNewReload; | 
 |   m_pieces = ttoLine.m_pieces; | 
 | } | 
 |  | 
 | CFDE_TTOLine::~CFDE_TTOLine() {} | 
 |  | 
 | int32_t CFDE_TTOLine::AddPiece(int32_t index, const FDE_TTOPIECE& ttoPiece) { | 
 |   if (index >= pdfium::CollectionSize<int32_t>(m_pieces)) { | 
 |     m_pieces.push_back(ttoPiece); | 
 |     return pdfium::CollectionSize<int32_t>(m_pieces); | 
 |   } | 
 |   m_pieces[index] = ttoPiece; | 
 |   return index; | 
 | } | 
 |  | 
 | int32_t CFDE_TTOLine::GetSize() const { | 
 |   return pdfium::CollectionSize<int32_t>(m_pieces); | 
 | } | 
 |  | 
 | FDE_TTOPIECE* CFDE_TTOLine::GetPtrAt(int32_t index) { | 
 |   return pdfium::IndexInBounds(m_pieces, index) ? &m_pieces[index] : nullptr; | 
 | } | 
 |  | 
 | void CFDE_TTOLine::RemoveLast(int32_t icount) { | 
 |   if (icount < 0) | 
 |     return; | 
 |   m_pieces.erase( | 
 |       m_pieces.end() - | 
 |           std::min(icount, pdfium::CollectionSize<int32_t>(m_pieces)), | 
 |       m_pieces.end()); | 
 | } |