|  | // Copyright 2017 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/fxfa/cxfa_textlayout.h" | 
|  |  | 
|  | #include <math.h> | 
|  |  | 
|  | #include <algorithm> | 
|  | #include <utility> | 
|  |  | 
|  | #include "core/fxcrt/check.h" | 
|  | #include "core/fxcrt/compiler_specific.h" | 
|  | #include "core/fxcrt/css/cfx_csscomputedstyle.h" | 
|  | #include "core/fxcrt/css/cfx_cssstyleselector.h" | 
|  | #include "core/fxcrt/notreached.h" | 
|  | #include "core/fxcrt/stl_util.h" | 
|  | #include "core/fxcrt/xml/cfx_xmlelement.h" | 
|  | #include "core/fxcrt/xml/cfx_xmlnode.h" | 
|  | #include "core/fxcrt/xml/cfx_xmltext.h" | 
|  | #include "core/fxge/cfx_fillrenderoptions.h" | 
|  | #include "core/fxge/cfx_graphstatedata.h" | 
|  | #include "core/fxge/cfx_path.h" | 
|  | #include "core/fxge/cfx_renderdevice.h" | 
|  | #include "core/fxge/text_char_pos.h" | 
|  | #include "fxjs/xfa/cjx_object.h" | 
|  | #include "xfa/fde/cfde_textout.h" | 
|  | #include "xfa/fgas/font/cfgas_gefont.h" | 
|  | #include "xfa/fgas/layout/cfgas_linkuserdata.h" | 
|  | #include "xfa/fgas/layout/cfgas_rtfbreak.h" | 
|  | #include "xfa/fgas/layout/cfgas_textuserdata.h" | 
|  | #include "xfa/fxfa/cxfa_ffdoc.h" | 
|  | #include "xfa/fxfa/cxfa_textparser.h" | 
|  | #include "xfa/fxfa/cxfa_textprovider.h" | 
|  | #include "xfa/fxfa/cxfa_texttabstopscontext.h" | 
|  | #include "xfa/fxfa/parser/cxfa_font.h" | 
|  | #include "xfa/fxfa/parser/cxfa_node.h" | 
|  | #include "xfa/fxfa/parser/cxfa_para.h" | 
|  |  | 
|  | namespace { | 
|  |  | 
|  | constexpr float kHeightTolerance = 0.001f; | 
|  |  | 
|  | void ProcessText(WideString* pText) { | 
|  | size_t iLen = pText->GetLength(); | 
|  | if (iLen == 0) { | 
|  | return; | 
|  | } | 
|  |  | 
|  | size_t iTrimFront = 0; | 
|  | { | 
|  | // Span's lifetime must end before ReleaseBuffer() below. | 
|  | pdfium::span<wchar_t> psz = pText->GetBuffer(iLen); | 
|  | wchar_t wPrev = 0; | 
|  | for (size_t i = 0; i < iLen; i++) { | 
|  | wchar_t wch = psz[i]; | 
|  | if (wch < 0x20) { | 
|  | wch = 0x20; | 
|  | } | 
|  | if (wch == 0x20 && wPrev == 0x20) { | 
|  | continue; | 
|  | } | 
|  |  | 
|  | wPrev = wch; | 
|  | psz[iTrimFront++] = wch; | 
|  | } | 
|  | } | 
|  | pText->ReleaseBuffer(iTrimFront); | 
|  | } | 
|  |  | 
|  | }  // namespace | 
|  |  | 
|  | CXFA_TextLayout::TextPiece::TextPiece() = default; | 
|  |  | 
|  | CXFA_TextLayout::TextPiece::~TextPiece() = default; | 
|  |  | 
|  | CXFA_TextLayout::PieceLine::PieceLine() = default; | 
|  |  | 
|  | CXFA_TextLayout::PieceLine::~PieceLine() = default; | 
|  |  | 
|  | CXFA_TextLayout::LoaderContext::LoaderContext() = default; | 
|  |  | 
|  | CXFA_TextLayout::LoaderContext::~LoaderContext() = default; | 
|  |  | 
|  | void CXFA_TextLayout::LoaderContext::Trace(cppgc::Visitor* visitor) const { | 
|  | visitor->Trace(pNode); | 
|  | } | 
|  |  | 
|  | CXFA_TextLayout::CXFA_TextLayout(CXFA_FFDoc* doc, | 
|  | CXFA_TextProvider* pTextProvider) | 
|  | : doc_(doc), | 
|  | text_provider_(pTextProvider), | 
|  | text_parser_(cppgc::MakeGarbageCollected<CXFA_TextParser>( | 
|  | doc->GetHeap()->GetAllocationHandle())) { | 
|  | DCHECK(text_provider_); | 
|  | } | 
|  |  | 
|  | CXFA_TextLayout::~CXFA_TextLayout() = default; | 
|  |  | 
|  | void CXFA_TextLayout::Trace(cppgc::Visitor* visitor) const { | 
|  | visitor->Trace(doc_); | 
|  | visitor->Trace(text_provider_); | 
|  | visitor->Trace(text_data_node_); | 
|  | visitor->Trace(text_parser_); | 
|  | visitor->Trace(loader_); | 
|  | } | 
|  |  | 
|  | void CXFA_TextLayout::Unload() { | 
|  | piece_lines_.clear(); | 
|  | break_.reset(); | 
|  | } | 
|  |  | 
|  | WideString CXFA_TextLayout::GetLinkURLAtPoint(const CFX_PointF& point) { | 
|  | for (const auto& pPieceLine : piece_lines_) { | 
|  | for (const auto& pPiece : pPieceLine->text_pieces_) { | 
|  | if (pPiece->pLinkData && pPiece->rtPiece.Contains(point)) { | 
|  | return pPiece->pLinkData->GetLinkURL(); | 
|  | } | 
|  | } | 
|  | } | 
|  | return WideString(); | 
|  | } | 
|  |  | 
|  | void CXFA_TextLayout::GetTextDataNode() { | 
|  | CXFA_Node* pNode = text_provider_->GetTextNode(&rich_text_); | 
|  | if (pNode && rich_text_) { | 
|  | text_parser_->Reset(); | 
|  | } | 
|  |  | 
|  | text_data_node_ = pNode; | 
|  | } | 
|  |  | 
|  | CFX_XMLNode* CXFA_TextLayout::GetXMLContainerNode() { | 
|  | if (!rich_text_) { | 
|  | return nullptr; | 
|  | } | 
|  |  | 
|  | CFX_XMLNode* pXMLRoot = text_data_node_->GetXMLMappingNode(); | 
|  | if (!pXMLRoot) { | 
|  | return nullptr; | 
|  | } | 
|  |  | 
|  | for (CFX_XMLNode* pXMLChild = pXMLRoot->GetFirstChild(); pXMLChild; | 
|  | pXMLChild = pXMLChild->GetNextSibling()) { | 
|  | CFX_XMLElement* pXMLElement = ToXMLElement(pXMLChild); | 
|  | if (!pXMLElement) { | 
|  | continue; | 
|  | } | 
|  | WideString wsTag = pXMLElement->GetLocalTagName(); | 
|  | if (wsTag.EqualsASCII("body") || wsTag.EqualsASCII("html")) { | 
|  | return pXMLChild; | 
|  | } | 
|  | } | 
|  | return nullptr; | 
|  | } | 
|  |  | 
|  | std::unique_ptr<CFGAS_RTFBreak> CXFA_TextLayout::CreateBreak(bool bDefault) { | 
|  | Mask<CFGAS_Break::LayoutStyle> dwStyle = CFGAS_Break::LayoutStyle::kExpandTab; | 
|  | if (!bDefault) { | 
|  | dwStyle |= CFGAS_Break::LayoutStyle::kPagination; | 
|  | } | 
|  |  | 
|  | auto pBreak = std::make_unique<CFGAS_RTFBreak>(dwStyle); | 
|  | pBreak->SetLineBreakTolerance(1); | 
|  | pBreak->SetFont(text_parser_->GetFont(doc_.Get(), text_provider_, nullptr)); | 
|  | pBreak->SetFontSize(text_parser_->GetFontSize(text_provider_, nullptr)); | 
|  | return pBreak; | 
|  | } | 
|  |  | 
|  | void CXFA_TextLayout::InitBreak(float fLineWidth) { | 
|  | CXFA_Para* para = text_provider_->GetParaIfExists(); | 
|  | float fStart = 0; | 
|  | float fStartPos = 0; | 
|  | if (para) { | 
|  | CFGAS_RTFBreak::LineAlignment iAlign = CFGAS_RTFBreak::LineAlignment::Left; | 
|  | switch (para->GetHorizontalAlign()) { | 
|  | case XFA_AttributeValue::Center: | 
|  | iAlign = CFGAS_RTFBreak::LineAlignment::Center; | 
|  | break; | 
|  | case XFA_AttributeValue::Right: | 
|  | iAlign = CFGAS_RTFBreak::LineAlignment::Right; | 
|  | break; | 
|  | case XFA_AttributeValue::Justify: | 
|  | iAlign = CFGAS_RTFBreak::LineAlignment::Justified; | 
|  | break; | 
|  | case XFA_AttributeValue::JustifyAll: | 
|  | iAlign = CFGAS_RTFBreak::LineAlignment::Distributed; | 
|  | break; | 
|  | case XFA_AttributeValue::Left: | 
|  | case XFA_AttributeValue::Radix: | 
|  | break; | 
|  | default: | 
|  | NOTREACHED(); | 
|  | } | 
|  | break_->SetAlignment(iAlign); | 
|  |  | 
|  | fStart = para->GetMarginLeft(); | 
|  | if (text_provider_->IsCheckButtonAndAutoWidth()) { | 
|  | if (iAlign != CFGAS_RTFBreak::LineAlignment::Left) { | 
|  | fLineWidth -= para->GetMarginRight(); | 
|  | } | 
|  | } else { | 
|  | fLineWidth -= para->GetMarginRight(); | 
|  | } | 
|  | if (fLineWidth < 0) { | 
|  | fLineWidth = fStart; | 
|  | } | 
|  |  | 
|  | fStartPos = fStart; | 
|  | float fIndent = para->GetTextIndent(); | 
|  | if (fIndent > 0) { | 
|  | fStartPos += fIndent; | 
|  | } | 
|  | } | 
|  |  | 
|  | break_->SetLineBoundary(fStart, fLineWidth); | 
|  | break_->SetLineStartPos(fStartPos); | 
|  |  | 
|  | CXFA_Font* font = text_provider_->GetFontIfExists(); | 
|  | if (font) { | 
|  | break_->SetHorizontalScale( | 
|  | static_cast<int32_t>(font->GetHorizontalScale())); | 
|  | break_->SetVerticalScale(static_cast<int32_t>(font->GetVerticalScale())); | 
|  | break_->SetCharSpace(font->GetLetterSpacing()); | 
|  | } | 
|  |  | 
|  | float fFontSize = text_parser_->GetFontSize(text_provider_, nullptr); | 
|  | break_->SetFontSize(fFontSize); | 
|  | break_->SetFont(text_parser_->GetFont(doc_.Get(), text_provider_, nullptr)); | 
|  | break_->SetLineBreakTolerance(fFontSize * 0.2f); | 
|  | } | 
|  |  | 
|  | void CXFA_TextLayout::InitBreak(CFX_CSSComputedStyle* pStyle, | 
|  | CFX_CSSDisplay eDisplay, | 
|  | float fLineWidth, | 
|  | const CFX_XMLNode* pXMLNode, | 
|  | CFX_CSSComputedStyle* pParentStyle) { | 
|  | if (!pStyle) { | 
|  | InitBreak(fLineWidth); | 
|  | return; | 
|  | } | 
|  |  | 
|  | if (eDisplay == CFX_CSSDisplay::Block || | 
|  | eDisplay == CFX_CSSDisplay::ListItem) { | 
|  | CFGAS_RTFBreak::LineAlignment iAlign = CFGAS_RTFBreak::LineAlignment::Left; | 
|  | switch (pStyle->GetTextAlign()) { | 
|  | case CFX_CSSTextAlign::Right: | 
|  | iAlign = CFGAS_RTFBreak::LineAlignment::Right; | 
|  | break; | 
|  | case CFX_CSSTextAlign::Center: | 
|  | iAlign = CFGAS_RTFBreak::LineAlignment::Center; | 
|  | break; | 
|  | case CFX_CSSTextAlign::Justify: | 
|  | iAlign = CFGAS_RTFBreak::LineAlignment::Justified; | 
|  | break; | 
|  | case CFX_CSSTextAlign::JustifyAll: | 
|  | iAlign = CFGAS_RTFBreak::LineAlignment::Distributed; | 
|  | break; | 
|  | default: | 
|  | break; | 
|  | } | 
|  | break_->SetAlignment(iAlign); | 
|  |  | 
|  | float fStart = 0; | 
|  | const CFX_CSSRect* pRect = pStyle->GetMarginWidth(); | 
|  | const CFX_CSSRect* pPaddingRect = pStyle->GetPaddingWidth(); | 
|  | if (pRect) { | 
|  | fStart = pRect->left.GetValue(); | 
|  | fLineWidth -= pRect->right.GetValue(); | 
|  | if (pPaddingRect) { | 
|  | fStart += pPaddingRect->left.GetValue(); | 
|  | fLineWidth -= pPaddingRect->right.GetValue(); | 
|  | } | 
|  | if (eDisplay == CFX_CSSDisplay::ListItem) { | 
|  | const CFX_CSSRect* pParRect = pParentStyle->GetMarginWidth(); | 
|  | const CFX_CSSRect* pParPaddingRect = pParentStyle->GetPaddingWidth(); | 
|  | if (pParRect) { | 
|  | fStart += pParRect->left.GetValue(); | 
|  | fLineWidth -= pParRect->right.GetValue(); | 
|  | if (pParPaddingRect) { | 
|  | fStart += pParPaddingRect->left.GetValue(); | 
|  | fLineWidth -= pParPaddingRect->right.GetValue(); | 
|  | } | 
|  | } | 
|  | CFX_CSSRect pNewRect; | 
|  | pNewRect.left.Set(CFX_CSSLengthUnit::Point, fStart); | 
|  | pNewRect.right.Set(CFX_CSSLengthUnit::Point, pRect->right.GetValue()); | 
|  | pNewRect.top.Set(CFX_CSSLengthUnit::Point, pRect->top.GetValue()); | 
|  | pNewRect.bottom.Set(CFX_CSSLengthUnit::Point, pRect->bottom.GetValue()); | 
|  | pStyle->SetMarginWidth(pNewRect); | 
|  | } | 
|  | } | 
|  | break_->SetLineBoundary(fStart, fLineWidth); | 
|  | float fIndent = pStyle->GetTextIndent().GetValue(); | 
|  | if (fIndent > 0) { | 
|  | fStart += fIndent; | 
|  | } | 
|  |  | 
|  | break_->SetLineStartPos(fStart); | 
|  | break_->SetTabWidth(text_parser_->GetTabInterval(pStyle)); | 
|  | if (!tabstop_context_) { | 
|  | tabstop_context_ = std::make_unique<CXFA_TextTabstopsContext>(); | 
|  | } | 
|  | text_parser_->GetTabstops(pStyle, tabstop_context_.get()); | 
|  | for (const auto& stop : tabstop_context_->tabstops_) { | 
|  | break_->AddPositionedTab(stop.fTabstops); | 
|  | } | 
|  | } | 
|  | float fFontSize = text_parser_->GetFontSize(text_provider_, pStyle); | 
|  | break_->SetFontSize(fFontSize); | 
|  | break_->SetLineBreakTolerance(fFontSize * 0.2f); | 
|  | break_->SetFont(text_parser_->GetFont(doc_.Get(), text_provider_, pStyle)); | 
|  | break_->SetHorizontalScale( | 
|  | text_parser_->GetHorScale(text_provider_, pStyle, pXMLNode)); | 
|  | break_->SetVerticalScale(text_parser_->GetVerScale(text_provider_, pStyle)); | 
|  | break_->SetCharSpace(pStyle->GetLetterSpacing().GetValue()); | 
|  | } | 
|  |  | 
|  | float CXFA_TextLayout::GetLayoutHeight() { | 
|  | if (!loader_) { | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | if (loader_->lineHeights.empty() && loader_->fWidth > 0) { | 
|  | CFX_SizeF szMax(loader_->fWidth, loader_->fHeight); | 
|  | loader_->bSaveLineHeight = true; | 
|  | loader_->fLastPos = 0; | 
|  | CFX_SizeF szDef = CalcSize(szMax, szMax); | 
|  | loader_->bSaveLineHeight = false; | 
|  | return szDef.height; | 
|  | } | 
|  |  | 
|  | float fHeight = loader_->fHeight; | 
|  | if (fHeight < 0.1f) { | 
|  | fHeight = 0; | 
|  | for (float value : loader_->lineHeights) { | 
|  | fHeight += value; | 
|  | } | 
|  | } | 
|  | return fHeight; | 
|  | } | 
|  |  | 
|  | float CXFA_TextLayout::StartLayout(float fWidth) { | 
|  | if (!loader_) { | 
|  | loader_ = cppgc::MakeGarbageCollected<LoaderContext>( | 
|  | doc_->GetHeap()->GetAllocationHandle()); | 
|  | } | 
|  |  | 
|  | if (fWidth < 0 || | 
|  | (loader_->fWidth > -1 && fabs(fWidth - loader_->fWidth) > 0)) { | 
|  | loader_->lineHeights.clear(); | 
|  | blocks_.clear(); | 
|  | Unload(); | 
|  | loader_->fStartLineOffset = 0; | 
|  | } | 
|  | loader_->fWidth = fWidth; | 
|  |  | 
|  | if (fWidth >= 0) { | 
|  | return fWidth; | 
|  | } | 
|  |  | 
|  | CFX_SizeF szMax; | 
|  |  | 
|  | loader_->bSaveLineHeight = true; | 
|  | loader_->fLastPos = 0; | 
|  | CFX_SizeF szDef = CalcSize(szMax, szMax); | 
|  | loader_->bSaveLineHeight = false; | 
|  | return szDef.width; | 
|  | } | 
|  |  | 
|  | float CXFA_TextLayout::DoLayout(float fTextHeight) { | 
|  | if (!loader_) { | 
|  | return fTextHeight; | 
|  | } | 
|  |  | 
|  | UpdateLoaderHeight(fTextHeight); | 
|  | return fTextHeight; | 
|  | } | 
|  |  | 
|  | float CXFA_TextLayout::DoSplitLayout(size_t szBlockIndex, | 
|  | float fCalcHeight, | 
|  | float fTextHeight) { | 
|  | if (!loader_) { | 
|  | return fCalcHeight; | 
|  | } | 
|  |  | 
|  | UpdateLoaderHeight(fTextHeight); | 
|  |  | 
|  | if (fCalcHeight < 0) { | 
|  | return fCalcHeight; | 
|  | } | 
|  |  | 
|  | has_block_ = true; | 
|  | if (blocks_.empty() && loader_->fHeight > 0) { | 
|  | float fHeight = fTextHeight - GetLayoutHeight(); | 
|  | if (fHeight > 0) { | 
|  | XFA_AttributeValue iAlign = text_parser_->GetVAlign(text_provider_); | 
|  | if (iAlign == XFA_AttributeValue::Middle) { | 
|  | fHeight /= 2.0f; | 
|  | } else if (iAlign != XFA_AttributeValue::Bottom) { | 
|  | fHeight = 0; | 
|  | } | 
|  | loader_->fStartLineOffset = fHeight; | 
|  | } | 
|  | } | 
|  |  | 
|  | float fLinePos = loader_->fStartLineOffset; | 
|  | size_t szLineIndex = 0; | 
|  | if (!blocks_.empty()) { | 
|  | if (szBlockIndex < blocks_.size()) { | 
|  | szLineIndex = blocks_[szBlockIndex].szIndex; | 
|  | } else { | 
|  | szLineIndex = GetNextIndexFromLastBlockData(); | 
|  | } | 
|  | for (size_t i = 0; i < std::min(szBlockIndex, loader_->blockHeights.size()); | 
|  | ++i) { | 
|  | fLinePos -= loader_->blockHeights[i].fHeight; | 
|  | } | 
|  | } | 
|  |  | 
|  | if (szLineIndex >= loader_->lineHeights.size()) { | 
|  | return fCalcHeight; | 
|  | } | 
|  |  | 
|  | if (loader_->lineHeights[szLineIndex] - fCalcHeight > kHeightTolerance) { | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | for (size_t i = szLineIndex; i < loader_->lineHeights.size(); ++i) { | 
|  | float fLineHeight = loader_->lineHeights[i]; | 
|  | if (fLinePos + fLineHeight - fCalcHeight <= kHeightTolerance) { | 
|  | fLinePos += fLineHeight; | 
|  | continue; | 
|  | } | 
|  |  | 
|  | if (szBlockIndex < blocks_.size()) { | 
|  | blocks_[szBlockIndex] = {szLineIndex, i - szLineIndex}; | 
|  | } else { | 
|  | blocks_.push_back({szLineIndex, i - szLineIndex}); | 
|  | } | 
|  |  | 
|  | if (i != szLineIndex) { | 
|  | return fLinePos; | 
|  | } | 
|  |  | 
|  | if (fCalcHeight > fLinePos) { | 
|  | return fCalcHeight; | 
|  | } | 
|  |  | 
|  | if (szBlockIndex < loader_->blockHeights.size() && | 
|  | loader_->blockHeights[szBlockIndex].szBlockIndex == szBlockIndex) { | 
|  | loader_->blockHeights[szBlockIndex].fHeight = fCalcHeight; | 
|  | } else { | 
|  | loader_->blockHeights.push_back({szBlockIndex, fCalcHeight}); | 
|  | } | 
|  | return fCalcHeight; | 
|  | } | 
|  | return fCalcHeight; | 
|  | } | 
|  |  | 
|  | size_t CXFA_TextLayout::CountBlocks() const { | 
|  | size_t szCount = blocks_.size(); | 
|  | return szCount > 0 ? szCount : 1; | 
|  | } | 
|  |  | 
|  | size_t CXFA_TextLayout::GetNextIndexFromLastBlockData() const { | 
|  | return blocks_.back().szIndex + blocks_.back().szLength; | 
|  | } | 
|  |  | 
|  | void CXFA_TextLayout::UpdateLoaderHeight(float fTextHeight) { | 
|  | loader_->fHeight = fTextHeight; | 
|  | if (loader_->fHeight < 0) { | 
|  | loader_->fHeight = GetLayoutHeight(); | 
|  | } | 
|  | } | 
|  |  | 
|  | CFX_SizeF CXFA_TextLayout::CalcSize(const CFX_SizeF& minSize, | 
|  | const CFX_SizeF& maxSize) { | 
|  | float width = maxSize.width; | 
|  | if (width < 1) { | 
|  | width = 0xFFFF; | 
|  | } | 
|  |  | 
|  | break_ = CreateBreak(false); | 
|  | float fLinePos = 0; | 
|  | lines_ = 0; | 
|  | max_width_ = 0; | 
|  | Loader(width, &fLinePos, false); | 
|  | if (fLinePos < 0.1f) { | 
|  | fLinePos = text_parser_->GetFontSize(text_provider_, nullptr); | 
|  | } | 
|  |  | 
|  | tabstop_context_.reset(); | 
|  | return CFX_SizeF(max_width_, fLinePos); | 
|  | } | 
|  |  | 
|  | float CXFA_TextLayout::Layout(const CFX_SizeF& size) { | 
|  | if (size.width < 1) { | 
|  | return 0.f; | 
|  | } | 
|  |  | 
|  | Unload(); | 
|  | break_ = CreateBreak(true); | 
|  | if (loader_) { | 
|  | loader_->iTotalLines = -1; | 
|  | loader_->nCharIdx = 0; | 
|  | } | 
|  |  | 
|  | lines_ = 0; | 
|  | float fLinePos = 0; | 
|  | Loader(size.width, &fLinePos, true); | 
|  | UpdateAlign(size.height, fLinePos); | 
|  | tabstop_context_.reset(); | 
|  | return fLinePos; | 
|  | } | 
|  |  | 
|  | bool CXFA_TextLayout::LayoutInternal(size_t szBlockIndex) { | 
|  | DCHECK(szBlockIndex < CountBlocks()); | 
|  |  | 
|  | if (!loader_ || loader_->fWidth < 1) { | 
|  | return false; | 
|  | } | 
|  |  | 
|  | loader_->iTotalLines = -1; | 
|  | lines_ = 0; | 
|  | float fLinePos = 0; | 
|  | CXFA_Node* pNode = nullptr; | 
|  | CFX_SizeF szText(loader_->fWidth, loader_->fHeight); | 
|  | if (szBlockIndex < loader_->blockHeights.size()) { | 
|  | return true; | 
|  | } | 
|  | if (szBlockIndex == loader_->blockHeights.size()) { | 
|  | Unload(); | 
|  | break_ = CreateBreak(true); | 
|  | fLinePos = loader_->fStartLineOffset; | 
|  | for (size_t i = 0; i < loader_->blockHeights.size(); ++i) { | 
|  | fLinePos -= loader_->blockHeights[i].fHeight; | 
|  | } | 
|  |  | 
|  | loader_->nCharIdx = 0; | 
|  | if (!blocks_.empty()) { | 
|  | loader_->iTotalLines = | 
|  | pdfium::checked_cast<int32_t>(blocks_[szBlockIndex].szLength); | 
|  | } | 
|  | Loader(szText.width, &fLinePos, true); | 
|  | if (blocks_.empty() && loader_->fStartLineOffset < 0.1f) { | 
|  | UpdateAlign(szText.height, fLinePos); | 
|  | } | 
|  | } else if (text_data_node_) { | 
|  | if (!blocks_.empty() && szBlockIndex < blocks_.size() - 1) { | 
|  | loader_->iTotalLines = | 
|  | pdfium::checked_cast<int32_t>(blocks_[szBlockIndex].szLength); | 
|  | } | 
|  | break_->Reset(); | 
|  | if (rich_text_) { | 
|  | CFX_XMLNode* pContainerNode = GetXMLContainerNode(); | 
|  | if (!pContainerNode) { | 
|  | return true; | 
|  | } | 
|  |  | 
|  | const CFX_XMLNode* pXMLNode = loader_->pXMLNode; | 
|  | if (!pXMLNode) { | 
|  | return true; | 
|  | } | 
|  |  | 
|  | const CFX_XMLNode* pSaveXMLNode = pXMLNode; | 
|  | for (; pXMLNode; pXMLNode = pXMLNode->GetNextSibling()) { | 
|  | if (!LoadRichText(pXMLNode, szText.width, &fLinePos, | 
|  | loader_->pParentStyle, true, nullptr, true, false, | 
|  | 0)) { | 
|  | break; | 
|  | } | 
|  | } | 
|  | while (!pXMLNode) { | 
|  | pXMLNode = pSaveXMLNode->GetParent(); | 
|  | if (pXMLNode == pContainerNode) { | 
|  | break; | 
|  | } | 
|  | if (!LoadRichText(pXMLNode, szText.width, &fLinePos, | 
|  | loader_->pParentStyle, true, nullptr, false, false, | 
|  | 0)) { | 
|  | break; | 
|  | } | 
|  | pSaveXMLNode = pXMLNode; | 
|  | pXMLNode = pXMLNode->GetNextSibling(); | 
|  | if (!pXMLNode) { | 
|  | continue; | 
|  | } | 
|  | for (; pXMLNode; pXMLNode = pXMLNode->GetNextSibling()) { | 
|  | if (!LoadRichText(pXMLNode, szText.width, &fLinePos, | 
|  | loader_->pParentStyle, true, nullptr, true, false, | 
|  | 0)) { | 
|  | break; | 
|  | } | 
|  | } | 
|  | } | 
|  | } else { | 
|  | pNode = loader_->pNode.Get(); | 
|  | if (!pNode) { | 
|  | return true; | 
|  | } | 
|  | LoadText(pNode, szText.width, &fLinePos, true); | 
|  | } | 
|  | } | 
|  | return true; | 
|  | } | 
|  |  | 
|  | void CXFA_TextLayout::ItemBlocks(const CFX_RectF& rtText, size_t szBlockIndex) { | 
|  | if (!loader_) { | 
|  | return; | 
|  | } | 
|  |  | 
|  | if (loader_->lineHeights.empty()) { | 
|  | return; | 
|  | } | 
|  |  | 
|  | float fLinePos = loader_->fStartLineOffset; | 
|  | size_t szLineIndex = 0; | 
|  | if (szBlockIndex > 0) { | 
|  | if (szBlockIndex <= loader_->blockHeights.size()) { | 
|  | for (size_t i = 0; i < szBlockIndex; ++i) { | 
|  | fLinePos -= loader_->blockHeights[i].fHeight; | 
|  | } | 
|  | } else { | 
|  | fLinePos = 0; | 
|  | } | 
|  | szLineIndex = GetNextIndexFromLastBlockData(); | 
|  | } | 
|  |  | 
|  | size_t i; | 
|  | for (i = szLineIndex; i < loader_->lineHeights.size(); ++i) { | 
|  | float fLineHeight = loader_->lineHeights[i]; | 
|  | if (fLinePos + fLineHeight - rtText.height > kHeightTolerance) { | 
|  | blocks_.push_back({szLineIndex, i - szLineIndex}); | 
|  | return; | 
|  | } | 
|  | fLinePos += fLineHeight; | 
|  | } | 
|  | if (i > szLineIndex) { | 
|  | blocks_.push_back({szLineIndex, i - szLineIndex}); | 
|  | } | 
|  | } | 
|  |  | 
|  | bool CXFA_TextLayout::DrawString(CFX_RenderDevice* pFxDevice, | 
|  | const CFX_Matrix& mtDoc2Device, | 
|  | const CFX_RectF& rtClip, | 
|  | size_t szBlockIndex) { | 
|  | if (!pFxDevice) { | 
|  | return false; | 
|  | } | 
|  |  | 
|  | pFxDevice->SaveState(); | 
|  | pFxDevice->SetClip_Rect(rtClip.GetOuterRect()); | 
|  |  | 
|  | if (piece_lines_.empty()) { | 
|  | size_t szBlockCount = CountBlocks(); | 
|  | for (size_t i = 0; i < szBlockCount; ++i) { | 
|  | LayoutInternal(i); | 
|  | } | 
|  | tabstop_context_.reset(); | 
|  | loader_.Clear(); | 
|  | } | 
|  |  | 
|  | std::vector<TextCharPos> char_pos(1); | 
|  | size_t szLineStart = 0; | 
|  | size_t szPieceLines = piece_lines_.size(); | 
|  | if (!blocks_.empty()) { | 
|  | if (szBlockIndex < blocks_.size()) { | 
|  | szLineStart = blocks_[szBlockIndex].szIndex; | 
|  | szPieceLines = blocks_[szBlockIndex].szLength; | 
|  | } else { | 
|  | szPieceLines = 0; | 
|  | } | 
|  | } | 
|  |  | 
|  | for (size_t i = 0; i < szPieceLines; ++i) { | 
|  | if (i + szLineStart >= piece_lines_.size()) { | 
|  | break; | 
|  | } | 
|  |  | 
|  | PieceLine* pPieceLine = piece_lines_[i + szLineStart].get(); | 
|  | for (size_t j = 0; j < pPieceLine->text_pieces_.size(); ++j) { | 
|  | const TextPiece* pPiece = pPieceLine->text_pieces_[j].get(); | 
|  | int32_t iChars = pPiece->iChars; | 
|  | if (fxcrt::CollectionSize<int32_t>(char_pos) < iChars) { | 
|  | char_pos.resize(iChars); | 
|  | } | 
|  | RenderString(pFxDevice, pPieceLine, j, char_pos, mtDoc2Device); | 
|  | } | 
|  | for (size_t j = 0; j < pPieceLine->text_pieces_.size(); ++j) { | 
|  | RenderPath(pFxDevice, pPieceLine, j, char_pos, mtDoc2Device); | 
|  | } | 
|  | } | 
|  | pFxDevice->RestoreState(false); | 
|  | return szPieceLines > 0; | 
|  | } | 
|  |  | 
|  | void CXFA_TextLayout::UpdateAlign(float fHeight, float fBottom) { | 
|  | fHeight -= fBottom; | 
|  | if (fHeight < 0.1f) { | 
|  | return; | 
|  | } | 
|  |  | 
|  | switch (text_parser_->GetVAlign(text_provider_)) { | 
|  | case XFA_AttributeValue::Middle: | 
|  | fHeight /= 2.0f; | 
|  | break; | 
|  | case XFA_AttributeValue::Bottom: | 
|  | break; | 
|  | default: | 
|  | return; | 
|  | } | 
|  |  | 
|  | for (const auto& pPieceLine : piece_lines_) { | 
|  | for (const auto& pPiece : pPieceLine->text_pieces_) { | 
|  | pPiece->rtPiece.top += fHeight; | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | void CXFA_TextLayout::Loader(float textWidth, | 
|  | float* pLinePos, | 
|  | bool bSavePieces) { | 
|  | GetTextDataNode(); | 
|  | if (!text_data_node_) { | 
|  | return; | 
|  | } | 
|  |  | 
|  | if (!rich_text_) { | 
|  | LoadText(text_data_node_, textWidth, pLinePos, bSavePieces); | 
|  | return; | 
|  | } | 
|  |  | 
|  | const CFX_XMLNode* pXMLContainer = GetXMLContainerNode(); | 
|  | if (!pXMLContainer) { | 
|  | return; | 
|  | } | 
|  |  | 
|  | if (!text_parser_->IsParsed()) { | 
|  | text_parser_->DoParse(pXMLContainer, text_provider_); | 
|  | } | 
|  |  | 
|  | auto pRootStyle = text_parser_->CreateRootStyle(text_provider_); | 
|  | LoadRichText(pXMLContainer, textWidth, pLinePos, std::move(pRootStyle), | 
|  | bSavePieces, nullptr, true, false, 0); | 
|  | } | 
|  |  | 
|  | void CXFA_TextLayout::LoadText(CXFA_Node* pNode, | 
|  | float textWidth, | 
|  | float* pLinePos, | 
|  | bool bSavePieces) { | 
|  | InitBreak(textWidth); | 
|  |  | 
|  | CXFA_Para* para = text_provider_->GetParaIfExists(); | 
|  | float fSpaceAbove = 0; | 
|  | if (para) { | 
|  | fSpaceAbove = para->GetSpaceAbove(); | 
|  | if (fSpaceAbove < 0.1f) { | 
|  | fSpaceAbove = 0; | 
|  | } | 
|  |  | 
|  | switch (para->GetVerticalAlign()) { | 
|  | case XFA_AttributeValue::Top: | 
|  | case XFA_AttributeValue::Middle: | 
|  | case XFA_AttributeValue::Bottom: { | 
|  | *pLinePos += fSpaceAbove; | 
|  | break; | 
|  | } | 
|  | default: | 
|  | NOTREACHED(); | 
|  | } | 
|  | } | 
|  |  | 
|  | WideString wsText = pNode->JSObject()->GetContent(false); | 
|  | wsText.TrimBack(L" "); | 
|  | bool bRet = AppendChar(wsText, pLinePos, fSpaceAbove, bSavePieces); | 
|  | if (bRet && loader_) { | 
|  | loader_->pNode = pNode; | 
|  | } else { | 
|  | EndBreak(CFGAS_Char::BreakType::kParagraph, pLinePos, bSavePieces); | 
|  | } | 
|  | } | 
|  |  | 
|  | bool CXFA_TextLayout::LoadRichText(const CFX_XMLNode* pXMLNode, | 
|  | float textWidth, | 
|  | float* pLinePos, | 
|  | RetainPtr<CFX_CSSComputedStyle> pParentStyle, | 
|  | bool bSavePieces, | 
|  | RetainPtr<CFGAS_LinkUserData> pLinkData, | 
|  | bool bEndBreak, | 
|  | bool bIsOl, | 
|  | int32_t iLiCount) { | 
|  | if (!pXMLNode) { | 
|  | return false; | 
|  | } | 
|  |  | 
|  | CXFA_TextParser::Context* pContext = | 
|  | text_parser_->GetParseContextFromMap(pXMLNode); | 
|  | CFX_CSSDisplay eDisplay = CFX_CSSDisplay::None; | 
|  | bool bContentNode = false; | 
|  | float fSpaceBelow = 0; | 
|  | RetainPtr<CFX_CSSComputedStyle> pStyle; | 
|  | WideString wsName; | 
|  | if (bEndBreak) { | 
|  | bool bCurOl = false; | 
|  | bool bCurLi = false; | 
|  | const CFX_XMLElement* pElement = nullptr; | 
|  | if (pContext) { | 
|  | if (pXMLNode->GetType() == CFX_XMLNode::Type::kText) { | 
|  | bContentNode = true; | 
|  | } else if (pXMLNode->GetType() == CFX_XMLNode::Type::kElement) { | 
|  | pElement = static_cast<const CFX_XMLElement*>(pXMLNode); | 
|  | wsName = pElement->GetLocalTagName(); | 
|  | } | 
|  | if (wsName.EqualsASCII("ol")) { | 
|  | bIsOl = true; | 
|  | bCurOl = true; | 
|  | } | 
|  |  | 
|  | eDisplay = pContext->GetDisplay(); | 
|  | if (eDisplay != CFX_CSSDisplay::Block && | 
|  | eDisplay != CFX_CSSDisplay::Inline && | 
|  | eDisplay != CFX_CSSDisplay::ListItem) { | 
|  | return true; | 
|  | } | 
|  |  | 
|  | pStyle = text_parser_->ComputeStyle(pXMLNode, pParentStyle); | 
|  | InitBreak(bContentNode ? pParentStyle.Get() : pStyle.Get(), eDisplay, | 
|  | textWidth, pXMLNode, pParentStyle.Get()); | 
|  | if ((eDisplay == CFX_CSSDisplay::Block || | 
|  | eDisplay == CFX_CSSDisplay::ListItem) && | 
|  | pStyle && | 
|  | (wsName.IsEmpty() || | 
|  | !(wsName.EqualsASCII("body") || wsName.EqualsASCII("html") || | 
|  | wsName.EqualsASCII("ol") || wsName.EqualsASCII("ul")))) { | 
|  | const CFX_CSSRect* pRect = pStyle->GetMarginWidth(); | 
|  | if (pRect) { | 
|  | *pLinePos += pRect->top.GetValue(); | 
|  | fSpaceBelow = pRect->bottom.GetValue(); | 
|  | } | 
|  | } | 
|  |  | 
|  | if (wsName.EqualsASCII("a")) { | 
|  | WideString wsLinkContent = pElement->GetAttribute(L"href"); | 
|  | if (!wsLinkContent.IsEmpty()) { | 
|  | pLinkData = pdfium::MakeRetain<CFGAS_LinkUserData>(wsLinkContent); | 
|  | } | 
|  | } | 
|  |  | 
|  | int32_t iTabCount = text_parser_->CountTabs( | 
|  | bContentNode ? pParentStyle.Get() : pStyle.Get()); | 
|  | bool bSpaceRun = text_parser_->IsSpaceRun( | 
|  | bContentNode ? pParentStyle.Get() : pStyle.Get()); | 
|  | WideString wsText; | 
|  | if (bContentNode && iTabCount == 0) { | 
|  | wsText = ToXMLText(pXMLNode)->GetText(); | 
|  | } else if (wsName.EqualsASCII("br")) { | 
|  | wsText = WideString(L'\n'); | 
|  | } else if (wsName.EqualsASCII("li")) { | 
|  | bCurLi = true; | 
|  | if (bIsOl) { | 
|  | wsText = WideString::Format(L"%d.  ", iLiCount); | 
|  | } else { | 
|  | wsText = 0x00B7 + WideStringView(L" "); | 
|  | } | 
|  | } else if (!bContentNode) { | 
|  | if (iTabCount > 0) { | 
|  | while (iTabCount-- > 0) { | 
|  | wsText += L'\t'; | 
|  | } | 
|  | } else { | 
|  | std::optional<WideString> obj = | 
|  | text_parser_->GetEmbeddedObj(text_provider_, pXMLNode); | 
|  | if (obj.has_value()) { | 
|  | wsText = obj.value(); | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | if (!wsText.IsEmpty() && bContentNode && !bSpaceRun) { | 
|  | ProcessText(&wsText); | 
|  | } | 
|  |  | 
|  | if (loader_) { | 
|  | if (wsText.GetLength() > 0 && loader_->bFilterSpace) { | 
|  | wsText.TrimFront(L" "); | 
|  | } | 
|  | if (CFX_CSSDisplay::Block == eDisplay) { | 
|  | loader_->bFilterSpace = true; | 
|  | } else if (CFX_CSSDisplay::Inline == eDisplay && | 
|  | loader_->bFilterSpace) { | 
|  | loader_->bFilterSpace = false; | 
|  | } else if (wsText.GetLength() > 0 && wsText.Back() == 0x20) { | 
|  | loader_->bFilterSpace = true; | 
|  | } else if (wsText.GetLength() != 0) { | 
|  | loader_->bFilterSpace = false; | 
|  | } | 
|  | } | 
|  |  | 
|  | if (wsText.GetLength() > 0) { | 
|  | if (!loader_ || loader_->nCharIdx == 0) { | 
|  | auto pUserData = pdfium::MakeRetain<CFGAS_TextUserData>( | 
|  | bContentNode ? pParentStyle : pStyle, pLinkData); | 
|  | break_->SetUserData(pUserData); | 
|  | } | 
|  |  | 
|  | if (AppendChar(wsText, pLinePos, 0, bSavePieces)) { | 
|  | if (loader_) { | 
|  | loader_->bFilterSpace = false; | 
|  | } | 
|  | if (!IsEnd(bSavePieces)) { | 
|  | return true; | 
|  | } | 
|  | if (loader_ && loader_->iTotalLines > -1) { | 
|  | loader_->pXMLNode = pXMLNode; | 
|  | loader_->pParentStyle = pParentStyle; | 
|  | } | 
|  | return false; | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | for (CFX_XMLNode* pChildNode = pXMLNode->GetFirstChild(); pChildNode; | 
|  | pChildNode = pChildNode->GetNextSibling()) { | 
|  | if (bCurOl) { | 
|  | iLiCount++; | 
|  | } | 
|  |  | 
|  | if (!LoadRichText(pChildNode, textWidth, pLinePos, | 
|  | pContext ? pStyle : pParentStyle, bSavePieces, | 
|  | pLinkData, true, bIsOl, iLiCount)) { | 
|  | return false; | 
|  | } | 
|  | } | 
|  |  | 
|  | if (loader_) { | 
|  | if (CFX_CSSDisplay::Block == eDisplay) { | 
|  | loader_->bFilterSpace = true; | 
|  | } | 
|  | } | 
|  | if (bCurLi) { | 
|  | EndBreak(CFGAS_Char::BreakType::kLine, pLinePos, bSavePieces); | 
|  | } | 
|  | } else { | 
|  | if (pContext) { | 
|  | eDisplay = pContext->GetDisplay(); | 
|  | } | 
|  | } | 
|  |  | 
|  | if (!pContext || bContentNode) { | 
|  | return true; | 
|  | } | 
|  |  | 
|  | CFGAS_Char::BreakType dwStatus = (eDisplay == CFX_CSSDisplay::Block) | 
|  | ? CFGAS_Char::BreakType::kParagraph | 
|  | : CFGAS_Char::BreakType::kPiece; | 
|  | EndBreak(dwStatus, pLinePos, bSavePieces); | 
|  | if (eDisplay == CFX_CSSDisplay::Block) { | 
|  | *pLinePos += fSpaceBelow; | 
|  | if (tabstop_context_) { | 
|  | tabstop_context_->RemoveAll(); | 
|  | } | 
|  | } | 
|  | if (!IsEnd(bSavePieces)) { | 
|  | return true; | 
|  | } | 
|  |  | 
|  | if (loader_ && loader_->iTotalLines > -1) { | 
|  | loader_->pXMLNode = pXMLNode->GetNextSibling(); | 
|  | loader_->pParentStyle = pParentStyle; | 
|  | } | 
|  | return false; | 
|  | } | 
|  |  | 
|  | bool CXFA_TextLayout::AppendChar(const WideString& wsText, | 
|  | float* pLinePos, | 
|  | float fSpaceAbove, | 
|  | bool bSavePieces) { | 
|  | CFGAS_Char::BreakType dwStatus = CFGAS_Char::BreakType::kNone; | 
|  | size_t iChar = loader_ ? loader_->nCharIdx : 0; | 
|  | size_t iLength = wsText.GetLength(); | 
|  | for (size_t i = iChar; i < iLength; i++) { | 
|  | wchar_t wch = wsText[i]; | 
|  | if (wch == 0xA0) { | 
|  | wch = 0x20; | 
|  | } | 
|  |  | 
|  | dwStatus = break_->AppendChar(wch); | 
|  | if (dwStatus != CFGAS_Char::BreakType::kNone && | 
|  | dwStatus != CFGAS_Char::BreakType::kPiece) { | 
|  | AppendTextLine(dwStatus, pLinePos, bSavePieces, false); | 
|  | if (IsEnd(bSavePieces)) { | 
|  | if (loader_) { | 
|  | loader_->nCharIdx = i; | 
|  | } | 
|  | return true; | 
|  | } | 
|  | if (dwStatus == CFGAS_Char::BreakType::kParagraph && rich_text_) { | 
|  | *pLinePos += fSpaceAbove; | 
|  | } | 
|  | } | 
|  | } | 
|  | if (loader_) { | 
|  | loader_->nCharIdx = 0; | 
|  | } | 
|  |  | 
|  | return false; | 
|  | } | 
|  |  | 
|  | bool CXFA_TextLayout::IsEnd(bool bSavePieces) { | 
|  | if (!bSavePieces) { | 
|  | return false; | 
|  | } | 
|  | if (loader_ && loader_->iTotalLines > 0) { | 
|  | return lines_ >= loader_->iTotalLines; | 
|  | } | 
|  | return false; | 
|  | } | 
|  |  | 
|  | void CXFA_TextLayout::EndBreak(CFGAS_Char::BreakType dwStatus, | 
|  | float* pLinePos, | 
|  | bool bSavePieces) { | 
|  | dwStatus = break_->EndBreak(dwStatus); | 
|  | if (dwStatus != CFGAS_Char::BreakType::kNone && | 
|  | dwStatus != CFGAS_Char::BreakType::kPiece) { | 
|  | AppendTextLine(dwStatus, pLinePos, bSavePieces, true); | 
|  | } | 
|  | } | 
|  |  | 
|  | void CXFA_TextLayout::DoTabstops(CFX_CSSComputedStyle* pStyle, | 
|  | PieceLine* pPieceLine) { | 
|  | if (!pStyle || !pPieceLine) { | 
|  | return; | 
|  | } | 
|  |  | 
|  | if (!tabstop_context_ || tabstop_context_->tabstops_.empty()) { | 
|  | return; | 
|  | } | 
|  |  | 
|  | int32_t iPieces = fxcrt::CollectionSize<int32_t>(pPieceLine->text_pieces_); | 
|  | if (iPieces == 0) { | 
|  | return; | 
|  | } | 
|  |  | 
|  | TextPiece* pPiece = pPieceLine->text_pieces_[iPieces - 1].get(); | 
|  | int32_t& iTabstopsIndex = tabstop_context_->tab_index_; | 
|  | int32_t iCount = text_parser_->CountTabs(pStyle); | 
|  | if (!fxcrt::IndexInBounds(tabstop_context_->tabstops_, iTabstopsIndex)) { | 
|  | return; | 
|  | } | 
|  |  | 
|  | if (iCount > 0) { | 
|  | iTabstopsIndex++; | 
|  | tabstop_context_->has_tabstops_ = true; | 
|  | float fRight = 0; | 
|  | if (iPieces > 1) { | 
|  | const TextPiece* p = pPieceLine->text_pieces_[iPieces - 2].get(); | 
|  | fRight = p->rtPiece.right(); | 
|  | } | 
|  | tabstop_context_->tab_width_ = | 
|  | pPiece->rtPiece.width + pPiece->rtPiece.left - fRight; | 
|  | } else if (iTabstopsIndex > -1) { | 
|  | float fLeft = 0; | 
|  | if (tabstop_context_->has_tabstops_) { | 
|  | uint32_t dwAlign = tabstop_context_->tabstops_[iTabstopsIndex].dwAlign; | 
|  | if (dwAlign == FX_HashCode_GetW(L"center")) { | 
|  | fLeft = pPiece->rtPiece.width / 2.0f; | 
|  | } else if (dwAlign == FX_HashCode_GetW(L"right") || | 
|  | dwAlign == FX_HashCode_GetW(L"before")) { | 
|  | fLeft = pPiece->rtPiece.width; | 
|  | } else if (dwAlign == FX_HashCode_GetW(L"decimal")) { | 
|  | int32_t iChars = pPiece->iChars; | 
|  | for (int32_t i = 0; i < iChars; i++) { | 
|  | if (pPiece->szText[i] == L'.') { | 
|  | break; | 
|  | } | 
|  |  | 
|  | fLeft += pPiece->Widths[i] / 20000.0f; | 
|  | } | 
|  | } | 
|  | tabstop_context_->left_ = std::min(fLeft, tabstop_context_->tab_width_); | 
|  | tabstop_context_->has_tabstops_ = false; | 
|  | tabstop_context_->tab_width_ = 0; | 
|  | } | 
|  | pPiece->rtPiece.left -= tabstop_context_->left_; | 
|  | } | 
|  | } | 
|  |  | 
|  | void CXFA_TextLayout::AppendTextLine(CFGAS_Char::BreakType dwStatus, | 
|  | float* pLinePos, | 
|  | bool bSavePieces, | 
|  | bool bEndBreak) { | 
|  | int32_t iPieces = break_->CountBreakPieces(); | 
|  | if (iPieces < 1) { | 
|  | return; | 
|  | } | 
|  |  | 
|  | RetainPtr<CFX_CSSComputedStyle> pStyle; | 
|  | if (bSavePieces) { | 
|  | auto pNew = std::make_unique<PieceLine>(); | 
|  | PieceLine* pPieceLine = pNew.get(); | 
|  | piece_lines_.push_back(std::move(pNew)); | 
|  | if (tabstop_context_) { | 
|  | tabstop_context_->Reset(); | 
|  | } | 
|  |  | 
|  | float fLineStep = 0; | 
|  | float fBaseLine = 0; | 
|  | int32_t i = 0; | 
|  | for (i = 0; i < iPieces; i++) { | 
|  | const CFGAS_BreakPiece* pPiece = break_->GetBreakPieceUnstable(i); | 
|  | const CFGAS_TextUserData* pUserData = pPiece->GetUserData(); | 
|  | if (pUserData) { | 
|  | pStyle = pUserData->style_; | 
|  | } | 
|  | float fVerScale = pPiece->GetVerticalScale() / 100.0f; | 
|  |  | 
|  | auto pTP = std::make_unique<TextPiece>(); | 
|  | pTP->iChars = pPiece->GetCharCount(); | 
|  | pTP->szText = pPiece->GetString(); | 
|  | pTP->Widths = pPiece->GetWidths(); | 
|  | pTP->iBidiLevel = pPiece->GetBidiLevel(); | 
|  | pTP->iHorScale = pPiece->GetHorizontalScale(); | 
|  | pTP->iVerScale = pPiece->GetVerticalScale(); | 
|  | pTP->iUnderline = | 
|  | text_parser_->GetUnderline(text_provider_, pStyle.Get()); | 
|  | pTP->iPeriod = | 
|  | text_parser_->GetUnderlinePeriod(text_provider_, pStyle.Get()); | 
|  | pTP->iLineThrough = | 
|  | text_parser_->GetLinethrough(text_provider_, pStyle.Get()); | 
|  | pTP->dwColor = text_parser_->GetColor(text_provider_, pStyle.Get()); | 
|  | pTP->pFont = | 
|  | text_parser_->GetFont(doc_.Get(), text_provider_, pStyle.Get()); | 
|  | pTP->fFontSize = text_parser_->GetFontSize(text_provider_, pStyle.Get()); | 
|  | pTP->rtPiece.left = pPiece->GetStartPos() / 20000.0f; | 
|  | pTP->rtPiece.width = pPiece->GetWidth() / 20000.0f; | 
|  | pTP->rtPiece.height = | 
|  | static_cast<float>(pPiece->GetFontSize()) * fVerScale / 20.0f; | 
|  | float fBaseLineTemp = | 
|  | text_parser_->GetBaseline(text_provider_, pStyle.Get()); | 
|  | pTP->rtPiece.top = fBaseLineTemp; | 
|  |  | 
|  | float fLineHeight = text_parser_->GetLineHeight( | 
|  | text_provider_, pStyle.Get(), lines_ == 0, fVerScale); | 
|  | if (fBaseLineTemp > 0) { | 
|  | float fLineHeightTmp = fBaseLineTemp + pTP->rtPiece.height; | 
|  | if (fLineHeight < fLineHeightTmp) { | 
|  | fLineHeight = fLineHeightTmp; | 
|  | } | 
|  | } | 
|  | fLineStep = std::max(fLineStep, fLineHeight); | 
|  | pTP->pLinkData = pUserData ? pUserData->link_data_ : nullptr; | 
|  | pPieceLine->text_pieces_.push_back(std::move(pTP)); | 
|  | DoTabstops(pStyle.Get(), pPieceLine); | 
|  | } | 
|  | for (const auto& pTP : pPieceLine->text_pieces_) { | 
|  | float& fTop = pTP->rtPiece.top; | 
|  | float fBaseLineTemp = fTop; | 
|  | fTop = *pLinePos + fLineStep - pTP->rtPiece.height - fBaseLineTemp; | 
|  | fTop = std::max(0.0f, fTop); | 
|  | } | 
|  | *pLinePos += fLineStep + fBaseLine; | 
|  | } else { | 
|  | float fLineStep = 0; | 
|  | float fLineWidth = 0; | 
|  | for (int32_t i = 0; i < iPieces; i++) { | 
|  | const CFGAS_BreakPiece* pPiece = break_->GetBreakPieceUnstable(i); | 
|  | const CFGAS_TextUserData* pUserData = pPiece->GetUserData(); | 
|  | if (pUserData) { | 
|  | pStyle = pUserData->style_; | 
|  | } | 
|  | float fVerScale = pPiece->GetVerticalScale() / 100.0f; | 
|  | float fBaseLine = text_parser_->GetBaseline(text_provider_, pStyle.Get()); | 
|  | float fLineHeight = text_parser_->GetLineHeight( | 
|  | text_provider_, pStyle.Get(), lines_ == 0, fVerScale); | 
|  | if (fBaseLine > 0) { | 
|  | float fLineHeightTmp = | 
|  | fBaseLine + | 
|  | static_cast<float>(pPiece->GetFontSize()) * fVerScale / 20.0f; | 
|  | if (fLineHeight < fLineHeightTmp) { | 
|  | fLineHeight = fLineHeightTmp; | 
|  | } | 
|  | } | 
|  | fLineStep = std::max(fLineStep, fLineHeight); | 
|  | fLineWidth += pPiece->GetWidth() / 20000.0f; | 
|  | } | 
|  | *pLinePos += fLineStep; | 
|  | max_width_ = std::max(max_width_, fLineWidth); | 
|  | if (loader_ && loader_->bSaveLineHeight) { | 
|  | float fHeight = *pLinePos - loader_->fLastPos; | 
|  | loader_->fLastPos = *pLinePos; | 
|  | loader_->lineHeights.push_back(fHeight); | 
|  | } | 
|  | } | 
|  |  | 
|  | break_->ClearBreakPieces(); | 
|  | if (dwStatus == CFGAS_Char::BreakType::kParagraph) { | 
|  | break_->Reset(); | 
|  | if (!pStyle && bEndBreak) { | 
|  | CXFA_Para* para = text_provider_->GetParaIfExists(); | 
|  | if (para) { | 
|  | float fStartPos = para->GetMarginLeft(); | 
|  | float fIndent = para->GetTextIndent(); | 
|  | if (fIndent > 0) { | 
|  | fStartPos += fIndent; | 
|  | } | 
|  |  | 
|  | float fSpaceBelow = para->GetSpaceBelow(); | 
|  | if (fSpaceBelow < 0.1f) { | 
|  | fSpaceBelow = 0; | 
|  | } | 
|  |  | 
|  | break_->SetLineStartPos(fStartPos); | 
|  | *pLinePos += fSpaceBelow; | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | if (pStyle) { | 
|  | float fStart = 0; | 
|  | const CFX_CSSRect* pRect = pStyle->GetMarginWidth(); | 
|  | if (pRect) { | 
|  | fStart = pRect->left.GetValue(); | 
|  | } | 
|  |  | 
|  | float fTextIndent = pStyle->GetTextIndent().GetValue(); | 
|  | if (fTextIndent < 0) { | 
|  | fStart -= fTextIndent; | 
|  | } | 
|  |  | 
|  | break_->SetLineStartPos(fStart); | 
|  | } | 
|  | lines_++; | 
|  | } | 
|  |  | 
|  | void CXFA_TextLayout::RenderString(CFX_RenderDevice* pDevice, | 
|  | PieceLine* pPieceLine, | 
|  | size_t szPiece, | 
|  | pdfium::span<TextCharPos> pCharPos, | 
|  | const CFX_Matrix& mtDoc2Device) { | 
|  | const TextPiece* pPiece = pPieceLine->text_pieces_[szPiece].get(); | 
|  | size_t szCount = GetDisplayPos(pPiece, pCharPos); | 
|  | if (szCount > 0) { | 
|  | CFDE_TextOut::DrawString(pDevice, pPiece->dwColor, pPiece->pFont, | 
|  | pCharPos.first(szCount), pPiece->fFontSize, | 
|  | mtDoc2Device); | 
|  | } | 
|  | pPieceLine->char_counts_.push_back(szCount); | 
|  | } | 
|  |  | 
|  | void CXFA_TextLayout::RenderPath(CFX_RenderDevice* pDevice, | 
|  | const PieceLine* pPieceLine, | 
|  | size_t szPiece, | 
|  | pdfium::span<TextCharPos> pCharPos, | 
|  | const CFX_Matrix& mtDoc2Device) { | 
|  | const TextPiece* pPiece = pPieceLine->text_pieces_[szPiece].get(); | 
|  | bool bNoUnderline = pPiece->iUnderline < 1 || pPiece->iUnderline > 2; | 
|  | bool bNoLineThrough = pPiece->iLineThrough < 1 || pPiece->iLineThrough > 2; | 
|  | if (bNoUnderline && bNoLineThrough) { | 
|  | return; | 
|  | } | 
|  |  | 
|  | CFX_Path path; | 
|  | size_t szChars = GetDisplayPos(pPiece, pCharPos); | 
|  | if (szChars > 0) { | 
|  | CFX_PointF pt1; | 
|  | CFX_PointF pt2; | 
|  | float fEndY = pCharPos[0].origin_.y + 1.05f; | 
|  | if (pPiece->iPeriod == XFA_AttributeValue::Word) { | 
|  | for (int32_t i = 0; i < pPiece->iUnderline; i++) { | 
|  | for (size_t j = 0; j < szChars; j++) { | 
|  | pt1.x = pCharPos[j].origin_.x; | 
|  | pt2.x = pt1.x + | 
|  | pCharPos[j].font_char_width_ * pPiece->fFontSize / 1000.0f; | 
|  | pt1.y = pt2.y = fEndY; | 
|  | path.AppendLine(pt1, pt2); | 
|  | } | 
|  | fEndY += 2.0f; | 
|  | } | 
|  | } else { | 
|  | pt1.x = pCharPos[0].origin_.x; | 
|  | pt2.x = | 
|  | pCharPos[szChars - 1].origin_.x + | 
|  | pCharPos[szChars - 1].font_char_width_ * pPiece->fFontSize / 1000.0f; | 
|  | for (int32_t i = 0; i < pPiece->iUnderline; i++) { | 
|  | pt1.y = pt2.y = fEndY; | 
|  | path.AppendLine(pt1, pt2); | 
|  | fEndY += 2.0f; | 
|  | } | 
|  | } | 
|  | fEndY = pCharPos[0].origin_.y - pPiece->rtPiece.height * 0.25f; | 
|  | pt1.x = pCharPos[0].origin_.x; | 
|  | pt2.x = | 
|  | pCharPos[szChars - 1].origin_.x + | 
|  | pCharPos[szChars - 1].font_char_width_ * pPiece->fFontSize / 1000.0f; | 
|  | for (int32_t i = 0; i < pPiece->iLineThrough; i++) { | 
|  | pt1.y = pt2.y = fEndY; | 
|  | path.AppendLine(pt1, pt2); | 
|  | fEndY += 2.0f; | 
|  | } | 
|  | } else { | 
|  | if (bNoLineThrough && | 
|  | (bNoUnderline || pPiece->iPeriod != XFA_AttributeValue::All)) { | 
|  | return; | 
|  | } | 
|  | bool bHasCount = false; | 
|  | size_t szPiecePrev = szPiece; | 
|  | size_t szPieceNext = szPiece; | 
|  | while (szPiecePrev > 0) { | 
|  | szPiecePrev--; | 
|  | if (pPieceLine->char_counts_[szPiecePrev] > 0) { | 
|  | bHasCount = true; | 
|  | break; | 
|  | } | 
|  | } | 
|  | if (!bHasCount) { | 
|  | return; | 
|  | } | 
|  |  | 
|  | bHasCount = false; | 
|  | while (szPieceNext + 1 < pPieceLine->text_pieces_.size()) { | 
|  | ++szPieceNext; | 
|  | if (pPieceLine->char_counts_[szPieceNext] > 0) { | 
|  | bHasCount = true; | 
|  | break; | 
|  | } | 
|  | } | 
|  | if (!bHasCount) { | 
|  | return; | 
|  | } | 
|  |  | 
|  | float fOrgX = 0.0f; | 
|  | float fEndX = 0.0f; | 
|  | pPiece = pPieceLine->text_pieces_[szPiecePrev].get(); | 
|  | szChars = GetDisplayPos(pPiece, pCharPos); | 
|  | if (szChars < 1) { | 
|  | return; | 
|  | } | 
|  |  | 
|  | fOrgX = | 
|  | pCharPos[szChars - 1].origin_.x + | 
|  | pCharPos[szChars - 1].font_char_width_ * pPiece->fFontSize / 1000.0f; | 
|  | pPiece = pPieceLine->text_pieces_[szPieceNext].get(); | 
|  | szChars = GetDisplayPos(pPiece, pCharPos); | 
|  | if (szChars < 1) { | 
|  | return; | 
|  | } | 
|  |  | 
|  | fEndX = pCharPos[0].origin_.x; | 
|  | CFX_PointF pt1; | 
|  | CFX_PointF pt2; | 
|  | pt1.x = fOrgX; | 
|  | pt2.x = fEndX; | 
|  | float fEndY = pCharPos[0].origin_.y + 1.05f; | 
|  | for (int32_t i = 0; i < pPiece->iUnderline; i++) { | 
|  | pt1.y = fEndY; | 
|  | pt2.y = fEndY; | 
|  | path.AppendLine(pt1, pt2); | 
|  | fEndY += 2.0f; | 
|  | } | 
|  | fEndY = pCharPos[0].origin_.y - pPiece->rtPiece.height * 0.25f; | 
|  | for (int32_t i = 0; i < pPiece->iLineThrough; i++) { | 
|  | pt1.y = fEndY; | 
|  | pt2.y = fEndY; | 
|  | path.AppendLine(pt1, pt2); | 
|  | fEndY += 2.0f; | 
|  | } | 
|  | } | 
|  |  | 
|  | const CFX_GraphStateData graph_state; | 
|  | pDevice->DrawPath(path, &mtDoc2Device, &graph_state, 0, pPiece->dwColor, | 
|  | CFX_FillRenderOptions()); | 
|  | } | 
|  |  | 
|  | size_t CXFA_TextLayout::GetDisplayPos(const TextPiece* pPiece, | 
|  | pdfium::span<TextCharPos> pCharPos) { | 
|  | if (!pPiece || pPiece->iChars < 1) { | 
|  | return 0; | 
|  | } | 
|  | return break_->GetDisplayPos(pPiece, pCharPos); | 
|  | } |