// Copyright 2014 The PDFium Authors // 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 #include #include "build/build_config.h" #include "core/fxcrt/fx_coordinates.h" #include "core/fxcrt/fx_extension.h" #include "core/fxcrt/fx_system.h" #include "core/fxcrt/stl_util.h" #include "core/fxge/cfx_font.h" #include "core/fxge/cfx_path.h" #include "core/fxge/cfx_renderdevice.h" #include "core/fxge/cfx_substfont.h" #include "core/fxge/cfx_textrenderoptions.h" #include "core/fxge/fx_font.h" #include "core/fxge/text_char_pos.h" #include "third_party/base/check.h" #include "third_party/base/check_op.h" #include "third_party/base/numerics/safe_conversions.h" #include "xfa/fgas/font/cfgas_gefont.h" #include "xfa/fgas/layout/cfgas_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& pFont, pdfium::span pCharPos, float fFontSize, const CFX_Matrix& matrix) { DCHECK(pFont); DCHECK(!pCharPos.empty()); CFX_Font* pFxFont = pFont->GetDevFont(); if (FontStyleIsItalic(pFont->GetFontStyles()) && !pFxFont->IsItalic()) { for (auto& pos : pCharPos) { static constexpr float mc = 0.267949f; pos.m_AdjustMatrix[2] += mc * pos.m_AdjustMatrix[0]; pos.m_AdjustMatrix[3] += mc * pos.m_AdjustMatrix[1]; } } #if !BUILDFLAG(IS_WIN) uint32_t dwFontStyle = pFont->GetFontStyles(); CFX_Font FxFont; auto SubstFxFont = std::make_unique(); SubstFxFont->m_Weight = FontStyleIsForceBold(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 RetainPtr pCurFont; TextCharPos* pCurCP = nullptr; int32_t iCurCount = 0; static constexpr CFX_TextRenderOptions kOptions(CFX_TextRenderOptions::kLcd); for (auto& pos : pCharPos) { RetainPtr pSTFont = pFont->GetSubstFont(static_cast(pos.m_GlyphIndex)); pos.m_GlyphIndex &= 0x00FFFFFF; pos.m_bFontStyle = false; if (pCurFont != pSTFont) { if (pCurFont) { pFxFont = pCurFont->GetDevFont(); CFX_Font* font; #if !BUILDFLAG(IS_WIN) FxFont.SetFace(pFxFont->GetFace()); FxFont.SetFontSpan(pFxFont->GetFontSpan()); font = &FxFont; #else font = pFxFont; #endif device->DrawNormalText(pdfium::make_span(pCurCP, iCurCount), font, -fFontSize, matrix, color, kOptions); } pCurFont = pSTFont; pCurCP = &pos; iCurCount = 1; } else { ++iCurCount; } } bool bRet = true; if (pCurFont && iCurCount) { pFxFont = pCurFont->GetDevFont(); CFX_Font* font; #if !BUILDFLAG(IS_WIN) FxFont.SetFace(pFxFont->GetFace()); FxFont.SetFontSpan(pFxFont->GetFontSpan()); font = &FxFont; #else font = pFxFont; #endif bRet = device->DrawNormalText(pdfium::make_span(pCurCP, iCurCount), font, -fFontSize, matrix, color, kOptions); } return bRet; } CFDE_TextOut::Piece::Piece() = default; CFDE_TextOut::Piece::Piece(const Piece& that) = default; CFDE_TextOut::Piece::~Piece() = default; CFDE_TextOut::CFDE_TextOut() : m_pTxtBreak(std::make_unique()) {} CFDE_TextOut::~CFDE_TextOut() = default; void CFDE_TextOut::SetFont(RetainPtr pFont) { DCHECK(pFont); m_pFont = std::move(pFont); m_pTxtBreak->SetFont(m_pFont); } void CFDE_TextOut::SetFontSize(float fFontSize) { DCHECK(fFontSize > 0); m_fFontSize = fFontSize; m_pTxtBreak->SetFontSize(fFontSize); } void CFDE_TextOut::SetStyles(const FDE_TextStyle& dwStyles) { m_Styles = dwStyles; m_dwTxtBkStyles = m_Styles.single_line_ ? CFGAS_Break::LayoutStyle::kSingleLine : CFGAS_Break::LayoutStyle::kNone; 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) { DCHECK(fLineSpace > 1.0f); m_fLineSpace = fLineSpace; } void CFDE_TextOut::SetLineBreakTolerance(float fTolerance) { m_fTolerance = fTolerance; m_pTxtBreak->SetLineBreakTolerance(m_fTolerance); } void CFDE_TextOut::CalcLogicSize(WideStringView str, CFX_SizeF* pSize) { CFX_RectF rtText(0.0f, 0.0f, pSize->width, pSize->height); CalcLogicSize(str, &rtText); *pSize = rtText.Size(); } void CFDE_TextOut::CalcLogicSize(WideStringView str, CFX_RectF* pRect) { if (str.IsEmpty()) { pRect->width = 0.0f; pRect->height = 0.0f; return; } DCHECK(m_pFont); DCHECK(m_fFontSize >= 1.0f); if (!m_Styles.single_line_) { if (pRect->Width() < 1.0f) pRect->width = m_fFontSize * 1000.0f; m_pTxtBreak->SetLineWidth(pRect->Width()); } m_iTotalLines = 0; float fWidth = 0.0f; float fHeight = 0.0f; float fStartPos = pRect->right(); CFGAS_Char::BreakType dwBreakStatus = CFGAS_Char::BreakType::kNone; 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(CFGAS_Char::BreakType::kParagraph); if (!CFX_BreakTypeNoneOrPiece(dwBreakStatus)) RetrieveLineWidth(dwBreakStatus, &fStartPos, &fWidth, &fHeight); m_pTxtBreak->Reset(); float fInc = pRect->Height() - fHeight; if (TextAlignmentVerticallyCentered(m_iAlignment)) fInc /= 2.0f; else if (IsTextAlignmentTop(m_iAlignment)) fInc = 0.0f; pRect->left += fStartPos; pRect->top += fInc; pRect->width = std::min(fWidth, pRect->Width()); pRect->height = fHeight; if (m_Styles.last_line_height_) pRect->height -= m_fLineSpace - m_fFontSize; } bool CFDE_TextOut::RetrieveLineWidth(CFGAS_Char::BreakType dwBreakStatus, float* pStartPos, float* pWidth, float* pHeight) { if (CFX_BreakTypeNoneOrPiece(dwBreakStatus)) return false; float fLineStep = std::max(m_fLineSpace, m_fFontSize); float fLineWidth = 0.0f; for (int32_t i = 0; i < m_pTxtBreak->CountBreakPieces(); i++) { const CFGAS_BreakPiece* pPiece = m_pTxtBreak->GetBreakPieceUnstable(i); fLineWidth += static_cast(pPiece->GetWidth()) / 20000.0f; *pStartPos = std::min(*pStartPos, static_cast(pPiece->GetStartPos()) / 20000.0f); } m_pTxtBreak->ClearBreakPieces(); if (dwBreakStatus == CFGAS_Char::BreakType::kParagraph) m_pTxtBreak->Reset(); if (!m_Styles.line_wrap_ && dwBreakStatus == CFGAS_Char::BreakType::kLine) { *pWidth += fLineWidth; } else { *pWidth = std::max(*pWidth, fLineWidth); *pHeight += fLineStep; } ++m_iTotalLines; return true; } void CFDE_TextOut::DrawLogicText(CFX_RenderDevice* device, const WideString& str, const CFX_RectF& rect) { DCHECK(m_pFont); DCHECK(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(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.GetOuterRect()); for (auto& line : m_ttoLines) { for (size_t i = 0; i < line.GetSize(); ++i) { const Piece* pPiece = line.GetPieceAtIndex(i); size_t szCount = GetDisplayPos(pPiece); if (szCount == 0) continue; CFDE_TextOut::DrawString(device, m_TxtColor, m_pFont, {m_CharPos.data(), szCount}, m_fFontSize, m_Matrix); } } device->RestoreState(false); } void CFDE_TextOut::LoadText(const WideString& str, const CFX_RectF& rect) { DCHECK(!str.IsEmpty()); m_wsText = str; if (m_CharWidths.size() < str.GetLength()) m_CharWidths.resize(str.GetLength(), 0); float fLineStep = std::max(m_fLineSpace, m_fFontSize); float fLineStop = rect.bottom(); m_fLinePos = rect.top; size_t start_char = 0; int32_t iPieceWidths = 0; CFGAS_Char::BreakType dwBreakStatus; bool bRet = false; for (const auto& wch : str) { dwBreakStatus = m_pTxtBreak->AppendChar(wch); if (CFX_BreakTypeNoneOrPiece(dwBreakStatus)) continue; bool bEndofLine = RetrievePieces(dwBreakStatus, false, rect, &start_char, &iPieceWidths); if (bEndofLine && (m_Styles.line_wrap_ || dwBreakStatus == CFGAS_Char::BreakType::kParagraph || dwBreakStatus == CFGAS_Char::BreakType::kPage)) { iPieceWidths = 0; ++m_iCurLine; m_fLinePos += fLineStep; } if (m_fLinePos + fLineStep > fLineStop) { size_t iCurLine = bEndofLine ? m_iCurLine - 1 : m_iCurLine; CHECK_LT(m_iCurLine, m_ttoLines.size()); m_ttoLines[iCurLine].set_new_reload(true); bRet = true; break; } } dwBreakStatus = m_pTxtBreak->EndBreak(CFGAS_Char::BreakType::kParagraph); if (!CFX_BreakTypeNoneOrPiece(dwBreakStatus) && !bRet) RetrievePieces(dwBreakStatus, false, rect, &start_char, &iPieceWidths); m_pTxtBreak->ClearBreakPieces(); m_pTxtBreak->Reset(); } bool CFDE_TextOut::RetrievePieces(CFGAS_Char::BreakType dwBreakStatus, bool bReload, const CFX_RectF& rect, size_t* pStartChar, int32_t* pPieceWidths) { float fLineStep = std::max(m_fLineSpace, m_fFontSize); bool bNeedReload = false; int32_t iLineWidth = FXSYS_roundf(rect.Width() * 20000.0f); int32_t iCount = m_pTxtBreak->CountBreakPieces(); size_t chars_to_skip = *pStartChar; for (int32_t i = 0; i < iCount; i++) { const CFGAS_BreakPiece* pPiece = m_pTxtBreak->GetBreakPieceUnstable(i); size_t iPieceChars = pPiece->GetLength(); if (chars_to_skip > iPieceChars) { chars_to_skip -= iPieceChars; continue; } size_t iChar = *pStartChar; int32_t iWidth = 0; size_t j = chars_to_skip; for (; j < iPieceChars; j++) { const CFGAS_Char* pTC = pPiece->GetChar(j); int32_t iCurCharWidth = std::max(pTC->m_iCharWidth, 0); if (m_Styles.single_line_ || !m_Styles.line_wrap_) { if (iLineWidth - *pPieceWidths - iWidth < iCurCharWidth) { bNeedReload = true; break; } } iWidth += iCurCharWidth; m_CharWidths[iChar++] = iCurCharWidth; } if (j == chars_to_skip && !bReload) { CHECK_LT(m_iCurLine, m_ttoLines.size()); m_ttoLines[m_iCurLine].set_new_reload(true); } else if (j > chars_to_skip) { Piece piece; piece.start_char = *pStartChar; piece.char_count = j - chars_to_skip; piece.char_styles = pPiece->GetCharStyles(); piece.bounds = CFX_RectF( rect.left + static_cast(pPiece->GetStartPos()) / 20000.0f, m_fLinePos, iWidth / 20000.0f, fLineStep); if (FX_IsOdd(pPiece->GetBidiLevel())) piece.char_styles |= FX_TXTCHARSTYLE_OddBidiLevel; AppendPiece(piece, bNeedReload, (bReload && i == iCount - 1)); } *pStartChar += iPieceChars; *pPieceWidths += iWidth; } m_pTxtBreak->ClearBreakPieces(); return m_Styles.single_line_ || m_Styles.line_wrap_ || bNeedReload || dwBreakStatus == CFGAS_Char::BreakType::kParagraph; } void CFDE_TextOut::AppendPiece(const Piece& piece, bool bNeedReload, bool bEnd) { if (m_iCurLine >= m_ttoLines.size()) { Line ttoLine; ttoLine.set_new_reload(bNeedReload); m_iCurPiece = ttoLine.AddPiece(m_iCurPiece, piece); m_ttoLines.push_back(ttoLine); m_iCurLine = m_ttoLines.size() - 1; } else { Line* pLine = &m_ttoLines[m_iCurLine]; pLine->set_new_reload(bNeedReload); m_iCurPiece = pLine->AddPiece(m_iCurPiece, piece); if (bEnd) { size_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) { size_t i = 0; for (auto& line : m_ttoLines) { if (line.new_reload()) { m_iCurLine = i; m_iCurPiece = 0; ReloadLinePiece(&line, rect); } ++i; } } void CFDE_TextOut::ReloadLinePiece(Line* line, const CFX_RectF& rect) { pdfium::span text_span = m_wsText.span(); size_t start_char = 0; size_t piece_count = line->GetSize(); int32_t piece_widths = 0; CFGAS_Char::BreakType break_status = CFGAS_Char::BreakType::kNone; for (size_t piece_index = 0; piece_index < piece_count; ++piece_index) { const Piece* piece = line->GetPieceAtIndex(piece_index); if (piece_index == 0) m_fLinePos = piece->bounds.top; start_char = piece->start_char; const size_t end = piece->start_char + piece->char_count; for (size_t char_index = start_char; char_index < end; ++char_index) { break_status = m_pTxtBreak->AppendChar(text_span[char_index]); if (!CFX_BreakTypeNoneOrPiece(break_status)) RetrievePieces(break_status, true, rect, &start_char, &piece_widths); } } break_status = m_pTxtBreak->EndBreak(CFGAS_Char::BreakType::kParagraph); if (!CFX_BreakTypeNoneOrPiece(break_status)) RetrievePieces(break_status, true, rect, &start_char, &piece_widths); m_pTxtBreak->Reset(); } void CFDE_TextOut::DoAlignment(const CFX_RectF& rect) { if (m_ttoLines.empty()) return; const Piece* pFirstPiece = m_ttoLines.back().GetPieceAtIndex(0); if (!pFirstPiece) return; float fInc = rect.bottom() - pFirstPiece->bounds.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) { for (size_t i = 0; i < line.GetSize(); ++i) line.GetPieceAtIndex(i)->bounds.top += fInc; } } size_t CFDE_TextOut::GetDisplayPos(const Piece* pPiece) { if (m_CharPos.size() < pPiece->char_count) m_CharPos.resize(pPiece->char_count, TextCharPos()); CFGAS_TxtBreak::Run tr; tr.wsStr = m_wsText.Substr(pPiece->start_char); tr.pWidths = &m_CharWidths[pPiece->start_char]; tr.iLength = pdfium::base::checked_cast(pPiece->char_count); tr.pFont = m_pFont; tr.fFontSize = m_fFontSize; tr.dwStyles = m_dwTxtBkStyles; tr.dwCharStyles = pPiece->char_styles; tr.pRect = &pPiece->bounds; return m_pTxtBreak->GetDisplayPos(tr, m_CharPos.data()); } CFDE_TextOut::Line::Line() = default; CFDE_TextOut::Line::Line(const Line& that) : new_reload_(that.new_reload_), pieces_(that.pieces_) {} CFDE_TextOut::Line::~Line() = default; size_t CFDE_TextOut::Line::AddPiece(size_t index, const Piece& piece) { if (index >= pieces_.size()) { pieces_.push_back(piece); return pieces_.size(); } pieces_[index] = piece; return index; } size_t CFDE_TextOut::Line::GetSize() const { return pieces_.size(); } const CFDE_TextOut::Piece* CFDE_TextOut::Line::GetPieceAtIndex( size_t index) const { CHECK(fxcrt::IndexInBounds(pieces_, index)); return &pieces_[index]; } CFDE_TextOut::Piece* CFDE_TextOut::Line::GetPieceAtIndex(size_t index) { CHECK(fxcrt::IndexInBounds(pieces_, index)); return &pieces_[index]; } void CFDE_TextOut::Line::RemoveLast(size_t count) { pieces_.erase(pieces_.end() - std::min(count, pieces_.size()), pieces_.end()); }