|  | // 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_textparser.h" | 
|  |  | 
|  | #include <algorithm> | 
|  | #include <utility> | 
|  |  | 
|  | #include "core/fxcrt/check.h" | 
|  | #include "core/fxcrt/css/cfx_css.h" | 
|  | #include "core/fxcrt/css/cfx_csscomputedstyle.h" | 
|  | #include "core/fxcrt/css/cfx_cssdeclaration.h" | 
|  | #include "core/fxcrt/css/cfx_cssstyleselector.h" | 
|  | #include "core/fxcrt/css/cfx_cssstylesheet.h" | 
|  | #include "core/fxcrt/fx_codepage.h" | 
|  | #include "core/fxcrt/notreached.h" | 
|  | #include "core/fxcrt/xml/cfx_xmlelement.h" | 
|  | #include "core/fxcrt/xml/cfx_xmlnode.h" | 
|  | #include "core/fxge/fx_font.h" | 
|  | #include "xfa/fgas/font/cfgas_fontmgr.h" | 
|  | #include "xfa/fgas/font/cfgas_gefont.h" | 
|  | #include "xfa/fxfa/cxfa_ffapp.h" | 
|  | #include "xfa/fxfa/cxfa_ffdoc.h" | 
|  | #include "xfa/fxfa/cxfa_fontmgr.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_measurement.h" | 
|  | #include "xfa/fxfa/parser/cxfa_para.h" | 
|  |  | 
|  | namespace { | 
|  |  | 
|  | enum class TabStopStatus { | 
|  | Error, | 
|  | EOS, | 
|  | None, | 
|  | Alignment, | 
|  | StartLeader, | 
|  | Leader, | 
|  | Location, | 
|  | }; | 
|  |  | 
|  | WideString GetLowerCaseElementAttributeOrDefault( | 
|  | const CFX_XMLElement* pElement, | 
|  | const WideString& wsName, | 
|  | const WideString& wsDefaultValue) { | 
|  | WideString ws = pElement->GetAttribute(wsName); | 
|  | if (ws.IsEmpty()) | 
|  | ws = wsDefaultValue; | 
|  | else | 
|  | ws.MakeLower(); | 
|  | return ws; | 
|  | } | 
|  |  | 
|  | }  // namespace | 
|  |  | 
|  | CXFA_TextParser::CXFA_TextParser() = default; | 
|  |  | 
|  | CXFA_TextParser::~CXFA_TextParser() = default; | 
|  |  | 
|  | void CXFA_TextParser::Reset() { | 
|  | m_mapXMLNodeToParseContext.clear(); | 
|  | m_bParsed = false; | 
|  | } | 
|  |  | 
|  | void CXFA_TextParser::InitCSSData(CXFA_TextProvider* pTextProvider) { | 
|  | if (!pTextProvider) | 
|  | return; | 
|  |  | 
|  | if (!m_pSelector) { | 
|  | m_pSelector = std::make_unique<CFX_CSSStyleSelector>(); | 
|  |  | 
|  | CXFA_Font* font = pTextProvider->GetFontIfExists(); | 
|  | m_pSelector->SetDefaultFontSize(font ? font->GetFontSize() : 10.0f); | 
|  | } | 
|  |  | 
|  | if (m_cssInitialized) | 
|  | return; | 
|  |  | 
|  | m_cssInitialized = true; | 
|  | auto uaSheet = LoadDefaultSheetStyle(); | 
|  | m_pSelector->SetUAStyleSheet(std::move(uaSheet)); | 
|  | m_pSelector->UpdateStyleIndex(); | 
|  | } | 
|  |  | 
|  | std::unique_ptr<CFX_CSSStyleSheet> CXFA_TextParser::LoadDefaultSheetStyle() { | 
|  | static const char kStyle[] = | 
|  | "html,body,ol,p,ul{display:block}" | 
|  | "li{display:list-item}" | 
|  | "ol,ul{padding-left:33px;margin:1.12em 0}" | 
|  | "ol{list-style-type:decimal}" | 
|  | "a{color:#0000ff;text-decoration:underline}" | 
|  | "b{font-weight:bolder}" | 
|  | "i{font-style:italic}" | 
|  | "sup{vertical-align:+15em;font-size:.66em}" | 
|  | "sub{vertical-align:-15em;font-size:.66em}"; | 
|  | WideString ws = WideString::FromASCII(kStyle); | 
|  | auto sheet = std::make_unique<CFX_CSSStyleSheet>(); | 
|  | if (!sheet->LoadBuffer(ws.AsStringView())) | 
|  | return nullptr; | 
|  |  | 
|  | return sheet; | 
|  | } | 
|  |  | 
|  | RetainPtr<CFX_CSSComputedStyle> CXFA_TextParser::CreateRootStyle( | 
|  | CXFA_TextProvider* pTextProvider) { | 
|  | CXFA_Para* para = pTextProvider->GetParaIfExists(); | 
|  | auto pStyle = m_pSelector->CreateComputedStyle(nullptr); | 
|  | float fLineHeight = 0; | 
|  | float fFontSize = 10; | 
|  |  | 
|  | if (para) { | 
|  | fLineHeight = para->GetLineHeight(); | 
|  | CFX_CSSLength indent; | 
|  | indent.Set(CFX_CSSLengthUnit::Point, para->GetTextIndent()); | 
|  | pStyle->SetTextIndent(indent); | 
|  | CFX_CSSTextAlign hAlign = CFX_CSSTextAlign::Left; | 
|  | switch (para->GetHorizontalAlign()) { | 
|  | case XFA_AttributeValue::Center: | 
|  | hAlign = CFX_CSSTextAlign::Center; | 
|  | break; | 
|  | case XFA_AttributeValue::Right: | 
|  | hAlign = CFX_CSSTextAlign::Right; | 
|  | break; | 
|  | case XFA_AttributeValue::Justify: | 
|  | hAlign = CFX_CSSTextAlign::Justify; | 
|  | break; | 
|  | case XFA_AttributeValue::JustifyAll: | 
|  | hAlign = CFX_CSSTextAlign::JustifyAll; | 
|  | break; | 
|  | case XFA_AttributeValue::Left: | 
|  | case XFA_AttributeValue::Radix: | 
|  | break; | 
|  | default: | 
|  | NOTREACHED(); | 
|  | } | 
|  | pStyle->SetTextAlign(hAlign); | 
|  | CFX_CSSRect rtMarginWidth; | 
|  | rtMarginWidth.left.Set(CFX_CSSLengthUnit::Point, para->GetMarginLeft()); | 
|  | rtMarginWidth.top.Set(CFX_CSSLengthUnit::Point, para->GetSpaceAbove()); | 
|  | rtMarginWidth.right.Set(CFX_CSSLengthUnit::Point, para->GetMarginRight()); | 
|  | rtMarginWidth.bottom.Set(CFX_CSSLengthUnit::Point, para->GetSpaceBelow()); | 
|  | pStyle->SetMarginWidth(rtMarginWidth); | 
|  | } | 
|  |  | 
|  | CXFA_Font* font = pTextProvider->GetFontIfExists(); | 
|  | if (font) { | 
|  | pStyle->SetColor(font->GetColor()); | 
|  | pStyle->SetFontStyle(font->IsItalic() ? CFX_CSSFontStyle::Italic | 
|  | : CFX_CSSFontStyle::Normal); | 
|  | pStyle->SetFontWeight(font->IsBold() ? FXFONT_FW_BOLD : FXFONT_FW_NORMAL); | 
|  | pStyle->SetNumberVerticalAlign(-font->GetBaselineShift()); | 
|  | fFontSize = font->GetFontSize(); | 
|  | CFX_CSSLength letterSpacing; | 
|  | letterSpacing.Set(CFX_CSSLengthUnit::Point, font->GetLetterSpacing()); | 
|  | pStyle->SetLetterSpacing(letterSpacing); | 
|  | Mask<CFX_CSSTEXTDECORATION> dwDecoration; | 
|  | if (font->GetLineThrough() > 0) | 
|  | dwDecoration |= CFX_CSSTEXTDECORATION::kLineThrough; | 
|  | if (font->GetUnderline() > 1) | 
|  | dwDecoration |= CFX_CSSTEXTDECORATION::kDouble; | 
|  | else if (font->GetUnderline() > 0) | 
|  | dwDecoration |= CFX_CSSTEXTDECORATION::kUnderline; | 
|  |  | 
|  | pStyle->SetTextDecoration(dwDecoration); | 
|  | } | 
|  | pStyle->SetLineHeight(fLineHeight); | 
|  | pStyle->SetFontSize(fFontSize); | 
|  | return pStyle; | 
|  | } | 
|  |  | 
|  | RetainPtr<CFX_CSSComputedStyle> CXFA_TextParser::CreateStyle( | 
|  | const CFX_CSSComputedStyle* pParentStyle) { | 
|  | auto pNewStyle = m_pSelector->CreateComputedStyle(pParentStyle); | 
|  | DCHECK(pNewStyle); | 
|  | if (!pParentStyle) | 
|  | return pNewStyle; | 
|  |  | 
|  | Mask<CFX_CSSTEXTDECORATION> dwDecoration = pParentStyle->GetTextDecoration(); | 
|  | float fBaseLine = 0; | 
|  | if (pParentStyle->GetVerticalAlign() == CFX_CSSVerticalAlign::Number) | 
|  | fBaseLine = pParentStyle->GetNumberVerticalAlign(); | 
|  |  | 
|  | pNewStyle->SetTextDecoration(dwDecoration); | 
|  | pNewStyle->SetNumberVerticalAlign(fBaseLine); | 
|  |  | 
|  | const CFX_CSSRect* pRect = pParentStyle->GetMarginWidth(); | 
|  | if (pRect) | 
|  | pNewStyle->SetMarginWidth(*pRect); | 
|  | return pNewStyle; | 
|  | } | 
|  |  | 
|  | RetainPtr<CFX_CSSComputedStyle> CXFA_TextParser::ComputeStyle( | 
|  | const CFX_XMLNode* pXMLNode, | 
|  | RetainPtr<const CFX_CSSComputedStyle> pParentStyle) { | 
|  | auto it = m_mapXMLNodeToParseContext.find(pXMLNode); | 
|  | if (it == m_mapXMLNodeToParseContext.end()) | 
|  | return nullptr; | 
|  |  | 
|  | Context* pContext = it->second.get(); | 
|  | if (!pContext) | 
|  | return nullptr; | 
|  |  | 
|  | pContext->SetParentStyle(pParentStyle); | 
|  |  | 
|  | auto tagProvider = ParseTagInfo(pXMLNode); | 
|  | if (tagProvider->m_bContent) | 
|  | return nullptr; | 
|  |  | 
|  | auto pStyle = CreateStyle(pParentStyle); | 
|  | m_pSelector->ComputeStyle(pContext->GetDecls(), | 
|  | tagProvider->GetAttribute(L"style"), | 
|  | tagProvider->GetAttribute(L"align"), pStyle.Get()); | 
|  | return pStyle; | 
|  | } | 
|  |  | 
|  | void CXFA_TextParser::DoParse(const CFX_XMLNode* pXMLContainer, | 
|  | CXFA_TextProvider* pTextProvider) { | 
|  | if (!pXMLContainer || !pTextProvider || m_bParsed) | 
|  | return; | 
|  |  | 
|  | m_bParsed = true; | 
|  | InitCSSData(pTextProvider); | 
|  | auto pRootStyle = CreateRootStyle(pTextProvider); | 
|  | ParseRichText(pXMLContainer, pRootStyle.Get()); | 
|  | } | 
|  |  | 
|  | void CXFA_TextParser::ParseRichText(const CFX_XMLNode* pXMLNode, | 
|  | const CFX_CSSComputedStyle* pParentStyle) { | 
|  | if (!pXMLNode) | 
|  | return; | 
|  |  | 
|  | auto tagProvider = ParseTagInfo(pXMLNode); | 
|  | if (!tagProvider->m_bTagAvailable) | 
|  | return; | 
|  |  | 
|  | RetainPtr<CFX_CSSComputedStyle> pNewStyle; | 
|  | if (!(tagProvider->GetTagName().EqualsASCII("body") && | 
|  | tagProvider->GetTagName().EqualsASCII("html"))) { | 
|  | auto pTextContext = std::make_unique<Context>(); | 
|  | CFX_CSSDisplay eDisplay = CFX_CSSDisplay::Inline; | 
|  | if (!tagProvider->m_bContent) { | 
|  | auto declArray = | 
|  | m_pSelector->MatchDeclarations(tagProvider->GetTagName()); | 
|  | pNewStyle = CreateStyle(pParentStyle); | 
|  | m_pSelector->ComputeStyle(declArray, tagProvider->GetAttribute(L"style"), | 
|  | tagProvider->GetAttribute(L"align"), | 
|  | pNewStyle.Get()); | 
|  |  | 
|  | if (!declArray.empty()) | 
|  | pTextContext->SetDecls(std::move(declArray)); | 
|  |  | 
|  | eDisplay = pNewStyle->GetDisplay(); | 
|  | } | 
|  | pTextContext->SetDisplay(eDisplay); | 
|  | m_mapXMLNodeToParseContext[pXMLNode] = std::move(pTextContext); | 
|  | } | 
|  |  | 
|  | for (CFX_XMLNode* pXMLChild = pXMLNode->GetFirstChild(); pXMLChild; | 
|  | pXMLChild = pXMLChild->GetNextSibling()) { | 
|  | ParseRichText(pXMLChild, pNewStyle.Get()); | 
|  | } | 
|  | } | 
|  |  | 
|  | bool CXFA_TextParser::TagValidate(const WideString& wsName) const { | 
|  | static const uint32_t s_XFATagName[] = { | 
|  | 0x61,        // a | 
|  | 0x62,        // b | 
|  | 0x69,        // i | 
|  | 0x70,        // p | 
|  | 0x0001f714,  // br | 
|  | 0x00022a55,  // li | 
|  | 0x000239bb,  // ol | 
|  | 0x00025881,  // ul | 
|  | 0x0bd37faa,  // sub | 
|  | 0x0bd37fb8,  // sup | 
|  | 0xa73e3af2,  // span | 
|  | 0xb182eaae,  // body | 
|  | 0xdb8ac455,  // html | 
|  | }; | 
|  | return std::binary_search(std::begin(s_XFATagName), std::end(s_XFATagName), | 
|  | FX_HashCode_GetLoweredW(wsName.AsStringView())); | 
|  | } | 
|  |  | 
|  | // static | 
|  | std::unique_ptr<CXFA_TextParser::TagProvider> CXFA_TextParser::ParseTagInfo( | 
|  | const CFX_XMLNode* pXMLNode) { | 
|  | auto tagProvider = std::make_unique<TagProvider>(); | 
|  | const CFX_XMLElement* pXMLElement = ToXMLElement(pXMLNode); | 
|  | if (pXMLElement) { | 
|  | WideString wsName = pXMLElement->GetLocalTagName(); | 
|  | tagProvider->SetTagName(wsName); | 
|  | tagProvider->m_bTagAvailable = TagValidate(wsName); | 
|  | WideString wsValue = pXMLElement->GetAttribute(L"style"); | 
|  | if (!wsValue.IsEmpty()) | 
|  | tagProvider->SetAttribute(L"style", wsValue); | 
|  |  | 
|  | return tagProvider; | 
|  | } | 
|  | if (pXMLNode->GetType() == CFX_XMLNode::Type::kText) { | 
|  | tagProvider->m_bTagAvailable = true; | 
|  | tagProvider->m_bContent = true; | 
|  | } | 
|  | return tagProvider; | 
|  | } | 
|  |  | 
|  | XFA_AttributeValue CXFA_TextParser::GetVAlign( | 
|  | CXFA_TextProvider* pTextProvider) const { | 
|  | CXFA_Para* para = pTextProvider->GetParaIfExists(); | 
|  | return para ? para->GetVerticalAlign() : XFA_AttributeValue::Top; | 
|  | } | 
|  |  | 
|  | float CXFA_TextParser::GetTabInterval( | 
|  | const CFX_CSSComputedStyle* pStyle) const { | 
|  | WideString wsValue; | 
|  | if (pStyle && pStyle->GetCustomStyle(L"tab-interval", &wsValue)) | 
|  | return CXFA_Measurement(wsValue.AsStringView()).ToUnit(XFA_Unit::Pt); | 
|  | return 36; | 
|  | } | 
|  |  | 
|  | int32_t CXFA_TextParser::CountTabs(const CFX_CSSComputedStyle* pStyle) const { | 
|  | WideString wsValue; | 
|  | if (pStyle && pStyle->GetCustomStyle(L"xfa-tab-count", &wsValue)) | 
|  | return wsValue.GetInteger(); | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | bool CXFA_TextParser::IsSpaceRun(const CFX_CSSComputedStyle* pStyle) const { | 
|  | WideString wsValue; | 
|  | return pStyle && pStyle->GetCustomStyle(L"xfa-spacerun", &wsValue) && | 
|  | wsValue.EqualsASCIINoCase("yes"); | 
|  | } | 
|  |  | 
|  | RetainPtr<CFGAS_GEFont> CXFA_TextParser::GetFont( | 
|  | CXFA_FFDoc* doc, | 
|  | CXFA_TextProvider* pTextProvider, | 
|  | const CFX_CSSComputedStyle* pStyle) const { | 
|  | WideString wsFamily = L"Courier"; | 
|  | uint32_t dwStyle = 0; | 
|  | CXFA_Font* font = pTextProvider->GetFontIfExists(); | 
|  | if (font) { | 
|  | wsFamily = font->GetTypeface(); | 
|  | if (font->IsBold()) | 
|  | dwStyle |= FXFONT_FORCE_BOLD; | 
|  | if (font->IsItalic()) | 
|  | dwStyle |= FXFONT_FORCE_BOLD; | 
|  | } | 
|  |  | 
|  | if (pStyle) { | 
|  | std::optional<WideString> last_family = pStyle->GetLastFontFamily(); | 
|  | if (last_family.has_value()) | 
|  | wsFamily = last_family.value(); | 
|  |  | 
|  | dwStyle = 0; | 
|  | if (pStyle->GetFontWeight() > FXFONT_FW_NORMAL) | 
|  | dwStyle |= FXFONT_FORCE_BOLD; | 
|  | if (pStyle->GetFontStyle() == CFX_CSSFontStyle::Italic) | 
|  | dwStyle |= FXFONT_ITALIC; | 
|  | } | 
|  |  | 
|  | CXFA_FontMgr* pFontMgr = doc->GetApp()->GetXFAFontMgr(); | 
|  | return pFontMgr->GetFont(doc, wsFamily, dwStyle); | 
|  | } | 
|  |  | 
|  | float CXFA_TextParser::GetFontSize(CXFA_TextProvider* pTextProvider, | 
|  | const CFX_CSSComputedStyle* pStyle) const { | 
|  | if (pStyle) | 
|  | return pStyle->GetFontSize(); | 
|  |  | 
|  | CXFA_Font* font = pTextProvider->GetFontIfExists(); | 
|  | return font ? font->GetFontSize() : 10; | 
|  | } | 
|  |  | 
|  | int32_t CXFA_TextParser::GetHorScale(CXFA_TextProvider* pTextProvider, | 
|  | const CFX_CSSComputedStyle* pStyle, | 
|  | const CFX_XMLNode* pXMLNode) const { | 
|  | if (pStyle) { | 
|  | WideString wsValue; | 
|  | if (pStyle->GetCustomStyle(L"xfa-font-horizontal-scale", &wsValue)) | 
|  | return wsValue.GetInteger(); | 
|  |  | 
|  | while (pXMLNode) { | 
|  | auto it = m_mapXMLNodeToParseContext.find(pXMLNode); | 
|  | if (it != m_mapXMLNodeToParseContext.end()) { | 
|  | Context* pContext = it->second.get(); | 
|  | if (pContext && pContext->GetParentStyle() && | 
|  | pContext->GetParentStyle()->GetCustomStyle( | 
|  | L"xfa-font-horizontal-scale", &wsValue)) { | 
|  | return wsValue.GetInteger(); | 
|  | } | 
|  | } | 
|  | pXMLNode = pXMLNode->GetParent(); | 
|  | } | 
|  | } | 
|  |  | 
|  | CXFA_Font* font = pTextProvider->GetFontIfExists(); | 
|  | return font ? static_cast<int32_t>(font->GetHorizontalScale()) : 100; | 
|  | } | 
|  |  | 
|  | int32_t CXFA_TextParser::GetVerScale(CXFA_TextProvider* pTextProvider, | 
|  | const CFX_CSSComputedStyle* pStyle) const { | 
|  | if (pStyle) { | 
|  | WideString wsValue; | 
|  | if (pStyle->GetCustomStyle(L"xfa-font-vertical-scale", &wsValue)) | 
|  | return wsValue.GetInteger(); | 
|  | } | 
|  |  | 
|  | CXFA_Font* font = pTextProvider->GetFontIfExists(); | 
|  | return font ? static_cast<int32_t>(font->GetVerticalScale()) : 100; | 
|  | } | 
|  |  | 
|  | int32_t CXFA_TextParser::GetUnderline( | 
|  | CXFA_TextProvider* pTextProvider, | 
|  | const CFX_CSSComputedStyle* pStyle) const { | 
|  | CXFA_Font* font = pTextProvider->GetFontIfExists(); | 
|  | if (!pStyle) | 
|  | return font ? font->GetUnderline() : 0; | 
|  |  | 
|  | const Mask<CFX_CSSTEXTDECORATION> dwDecoration = pStyle->GetTextDecoration(); | 
|  | if (dwDecoration & CFX_CSSTEXTDECORATION::kDouble) | 
|  | return 2; | 
|  | if (dwDecoration & CFX_CSSTEXTDECORATION::kUnderline) | 
|  | return 1; | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | XFA_AttributeValue CXFA_TextParser::GetUnderlinePeriod( | 
|  | CXFA_TextProvider* pTextProvider, | 
|  | const CFX_CSSComputedStyle* pStyle) const { | 
|  | WideString wsValue; | 
|  | if (pStyle && pStyle->GetCustomStyle(L"underlinePeriod", &wsValue)) { | 
|  | return wsValue.EqualsASCII("word") ? XFA_AttributeValue::Word | 
|  | : XFA_AttributeValue::All; | 
|  | } | 
|  | CXFA_Font* font = pTextProvider->GetFontIfExists(); | 
|  | return font ? font->GetUnderlinePeriod() : XFA_AttributeValue::All; | 
|  | } | 
|  |  | 
|  | int32_t CXFA_TextParser::GetLinethrough( | 
|  | CXFA_TextProvider* pTextProvider, | 
|  | const CFX_CSSComputedStyle* pStyle) const { | 
|  | if (pStyle) { | 
|  | const Mask<CFX_CSSTEXTDECORATION> dwDecoration = | 
|  | pStyle->GetTextDecoration(); | 
|  | return (dwDecoration & CFX_CSSTEXTDECORATION::kLineThrough) ? 1 : 0; | 
|  | } | 
|  | CXFA_Font* font = pTextProvider->GetFontIfExists(); | 
|  | return font ? font->GetLineThrough() : 0; | 
|  | } | 
|  |  | 
|  | FX_ARGB CXFA_TextParser::GetColor(CXFA_TextProvider* pTextProvider, | 
|  | const CFX_CSSComputedStyle* pStyle) const { | 
|  | if (pStyle) | 
|  | return pStyle->GetColor(); | 
|  |  | 
|  | CXFA_Font* font = pTextProvider->GetFontIfExists(); | 
|  | return font ? font->GetColor() : 0xFF000000; | 
|  | } | 
|  |  | 
|  | float CXFA_TextParser::GetBaseline(CXFA_TextProvider* pTextProvider, | 
|  | const CFX_CSSComputedStyle* pStyle) const { | 
|  | if (pStyle) { | 
|  | if (pStyle->GetVerticalAlign() == CFX_CSSVerticalAlign::Number) | 
|  | return pStyle->GetNumberVerticalAlign(); | 
|  | } else { | 
|  | CXFA_Font* font = pTextProvider->GetFontIfExists(); | 
|  | if (font) | 
|  | return font->GetBaselineShift(); | 
|  | } | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | float CXFA_TextParser::GetLineHeight(CXFA_TextProvider* pTextProvider, | 
|  | const CFX_CSSComputedStyle* pStyle, | 
|  | bool bFirst, | 
|  | float fVerScale) const { | 
|  | float fLineHeight = 0; | 
|  | if (pStyle) { | 
|  | fLineHeight = pStyle->GetLineHeight(); | 
|  | } else { | 
|  | CXFA_Para* para = pTextProvider->GetParaIfExists(); | 
|  | if (para) | 
|  | fLineHeight = para->GetLineHeight(); | 
|  | } | 
|  |  | 
|  | if (bFirst) { | 
|  | float fFontSize = GetFontSize(pTextProvider, pStyle); | 
|  | if (fLineHeight < 0.1f) | 
|  | fLineHeight = fFontSize; | 
|  | else | 
|  | fLineHeight = std::min(fLineHeight, fFontSize); | 
|  | } else if (fLineHeight < 0.1f) { | 
|  | fLineHeight = GetFontSize(pTextProvider, pStyle) * 1.2f; | 
|  | } | 
|  | fLineHeight *= fVerScale; | 
|  | return fLineHeight; | 
|  | } | 
|  |  | 
|  | std::optional<WideString> CXFA_TextParser::GetEmbeddedObj( | 
|  | const CXFA_TextProvider* pTextProvider, | 
|  | const CFX_XMLNode* pXMLNode) { | 
|  | if (!pXMLNode) | 
|  | return std::nullopt; | 
|  |  | 
|  | const CFX_XMLElement* pElement = ToXMLElement(pXMLNode); | 
|  | if (!pElement) | 
|  | return std::nullopt; | 
|  |  | 
|  | WideString wsAttr = pElement->GetAttribute(L"xfa:embed"); | 
|  | if (wsAttr.IsEmpty()) | 
|  | return std::nullopt; | 
|  |  | 
|  | if (wsAttr[0] == L'#') | 
|  | wsAttr.Delete(0); | 
|  |  | 
|  | WideString ws = | 
|  | GetLowerCaseElementAttributeOrDefault(pElement, L"xfa:embedType", L"som"); | 
|  | if (!ws.EqualsASCII("uri")) | 
|  | return std::nullopt; | 
|  |  | 
|  | ws = GetLowerCaseElementAttributeOrDefault(pElement, L"xfa:embedMode", | 
|  | L"formatted"); | 
|  | if (!(ws.EqualsASCII("raw") || ws.EqualsASCII("formatted"))) | 
|  | return std::nullopt; | 
|  |  | 
|  | return pTextProvider->GetEmbeddedObj(wsAttr); | 
|  | } | 
|  |  | 
|  | CXFA_TextParser::Context* CXFA_TextParser::GetParseContextFromMap( | 
|  | const CFX_XMLNode* pXMLNode) { | 
|  | auto it = m_mapXMLNodeToParseContext.find(pXMLNode); | 
|  | return it != m_mapXMLNodeToParseContext.end() ? it->second.get() : nullptr; | 
|  | } | 
|  |  | 
|  | bool CXFA_TextParser::GetTabstops(const CFX_CSSComputedStyle* pStyle, | 
|  | CXFA_TextTabstopsContext* pTabstopContext) { | 
|  | if (!pStyle || !pTabstopContext) | 
|  | return false; | 
|  |  | 
|  | WideString wsValue; | 
|  | if (!pStyle->GetCustomStyle(L"xfa-tab-stops", &wsValue) && | 
|  | !pStyle->GetCustomStyle(L"tab-stops", &wsValue)) { | 
|  | return false; | 
|  | } | 
|  |  | 
|  | pdfium::span<const wchar_t> spTabStops = wsValue.span(); | 
|  | size_t iCur = 0; | 
|  | size_t iLast = 0; | 
|  | WideString wsAlign; | 
|  | TabStopStatus eStatus = TabStopStatus::None; | 
|  | while (iCur < spTabStops.size()) { | 
|  | wchar_t ch = spTabStops[iCur]; | 
|  | switch (eStatus) { | 
|  | case TabStopStatus::None: | 
|  | if (ch <= ' ') { | 
|  | iCur++; | 
|  | } else { | 
|  | eStatus = TabStopStatus::Alignment; | 
|  | iLast = iCur; | 
|  | } | 
|  | break; | 
|  | case TabStopStatus::Alignment: | 
|  | if (ch == ' ') { | 
|  | wsAlign = WideStringView(spTabStops.subspan(iLast, iCur - iLast)); | 
|  | eStatus = TabStopStatus::StartLeader; | 
|  | iCur++; | 
|  | while (iCur < spTabStops.size() && spTabStops[iCur] <= ' ') | 
|  | iCur++; | 
|  | iLast = iCur; | 
|  | } else { | 
|  | iCur++; | 
|  | } | 
|  | break; | 
|  | case TabStopStatus::StartLeader: | 
|  | if (ch != 'l') { | 
|  | eStatus = TabStopStatus::Location; | 
|  | } else { | 
|  | int32_t iCount = 0; | 
|  | while (iCur < spTabStops.size()) { | 
|  | ch = spTabStops[iCur]; | 
|  | iCur++; | 
|  | if (ch == '(') { | 
|  | iCount++; | 
|  | } else if (ch == ')') { | 
|  | iCount--; | 
|  | if (iCount == 0) | 
|  | break; | 
|  | } | 
|  | } | 
|  | while (iCur < spTabStops.size() && spTabStops[iCur] <= ' ') | 
|  | iCur++; | 
|  |  | 
|  | iLast = iCur; | 
|  | eStatus = TabStopStatus::Location; | 
|  | } | 
|  | break; | 
|  | case TabStopStatus::Location: | 
|  | if (ch == ' ') { | 
|  | uint32_t dwHashCode = FX_HashCode_GetLoweredW(wsAlign.AsStringView()); | 
|  | CXFA_Measurement ms( | 
|  | WideStringView(spTabStops.subspan(iLast, iCur - iLast))); | 
|  | float fPos = ms.ToUnit(XFA_Unit::Pt); | 
|  | pTabstopContext->Append(dwHashCode, fPos); | 
|  | wsAlign.clear(); | 
|  | eStatus = TabStopStatus::None; | 
|  | } | 
|  | iCur++; | 
|  | break; | 
|  | default: | 
|  | break; | 
|  | } | 
|  | } | 
|  |  | 
|  | if (!wsAlign.IsEmpty()) { | 
|  | uint32_t dwHashCode = FX_HashCode_GetLoweredW(wsAlign.AsStringView()); | 
|  | CXFA_Measurement ms( | 
|  | WideStringView(spTabStops.subspan(iLast, iCur - iLast))); | 
|  | float fPos = ms.ToUnit(XFA_Unit::Pt); | 
|  | pTabstopContext->Append(dwHashCode, fPos); | 
|  | } | 
|  | return true; | 
|  | } | 
|  |  | 
|  | CXFA_TextParser::TagProvider::TagProvider() = default; | 
|  |  | 
|  | CXFA_TextParser::TagProvider::~TagProvider() = default; | 
|  |  | 
|  | CXFA_TextParser::Context::Context() = default; | 
|  |  | 
|  | CXFA_TextParser::Context::~Context() = default; | 
|  |  | 
|  | void CXFA_TextParser::Context::SetParentStyle( | 
|  | RetainPtr<const CFX_CSSComputedStyle> style) { | 
|  | m_pParentStyle = std::move(style); | 
|  | } | 
|  |  | 
|  | void CXFA_TextParser::Context::SetDecls( | 
|  | std::vector<const CFX_CSSDeclaration*>&& decl) { | 
|  | decls_ = std::move(decl); | 
|  | } |