// Copyright 2017 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/fxfa/cxfa_textparser.h" #include <algorithm> #include <utility> #include <vector> #include "core/fxcrt/css/cfx_css.h" #include "core/fxcrt/css/cfx_csscomputedstyle.h" #include "core/fxcrt/css/cfx_cssstyleselector.h" #include "core/fxcrt/css/cfx_cssstylesheet.h" #include "core/fxcrt/fx_codepage.h" #include "core/fxcrt/xml/cfx_xmlelement.h" #include "core/fxcrt/xml/cfx_xmlnode.h" #include "third_party/base/ptr_util.h" #include "xfa/fgas/font/cfgas_fontmgr.h" #include "xfa/fxfa/cxfa_ffapp.h" #include "xfa/fxfa/cxfa_ffdoc.h" #include "xfa/fxfa/cxfa_fontmgr.h" #include "xfa/fxfa/cxfa_textparsecontext.h" #include "xfa/fxfa/cxfa_textprovider.h" #include "xfa/fxfa/cxfa_texttabstopscontext.h" #include "xfa/fxfa/parser/cxfa_measurement.h" namespace { enum class TabStopStatus { Error, EOS, None, Alignment, StartLeader, Leader, Location, }; } // namespace CXFA_TextParser::CXFA_TextParser() : m_bParsed(false), m_cssInitialized(false) {} CXFA_TextParser::~CXFA_TextParser() {} 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 = pdfium::MakeUnique<CFX_CSSStyleSelector>(); CXFA_FontData fontData = pTextProvider->GetFontData(); m_pSelector->SetDefFontSize(fontData.HasValidNode() ? fontData.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 wchar_t s_pStyle[] = L"html,body,ol,p,ul{display:block}" L"li{display:list-item}" L"ol,ul{padding-left:33px;margin:1.12em 0}" L"ol{list-style-type:decimal}" L"a{color:#0000ff;text-decoration:underline}" L"b{font-weight:bolder}" L"i{font-style:italic}" L"sup{vertical-align:+15em;font-size:.66em}" L"sub{vertical-align:-15em;font-size:.66em}"; auto sheet = pdfium::MakeUnique<CFX_CSSStyleSheet>(); return sheet->LoadBuffer(s_pStyle, wcslen(s_pStyle)) ? std::move(sheet) : nullptr; } RetainPtr<CFX_CSSComputedStyle> CXFA_TextParser::CreateRootStyle( CXFA_TextProvider* pTextProvider) { CXFA_ParaData paraData = pTextProvider->GetParaData(); auto pStyle = m_pSelector->CreateComputedStyle(nullptr); float fLineHeight = 0; float fFontSize = 10; if (paraData.HasValidNode()) { fLineHeight = paraData.GetLineHeight(); CFX_CSSLength indent; indent.Set(CFX_CSSLengthUnit::Point, paraData.GetTextIndent()); pStyle->SetTextIndent(indent); CFX_CSSTextAlign hAlign = CFX_CSSTextAlign::Left; switch (paraData.GetHorizontalAlign()) { case XFA_AttributeEnum::Center: hAlign = CFX_CSSTextAlign::Center; break; case XFA_AttributeEnum::Right: hAlign = CFX_CSSTextAlign::Right; break; case XFA_AttributeEnum::Justify: hAlign = CFX_CSSTextAlign::Justify; break; case XFA_AttributeEnum::JustifyAll: hAlign = CFX_CSSTextAlign::JustifyAll; break; case XFA_AttributeEnum::Left: case XFA_AttributeEnum::Radix: break; default: NOTREACHED(); break; } pStyle->SetTextAlign(hAlign); CFX_CSSRect rtMarginWidth; rtMarginWidth.left.Set(CFX_CSSLengthUnit::Point, paraData.GetMarginLeft()); rtMarginWidth.top.Set(CFX_CSSLengthUnit::Point, paraData.GetSpaceAbove()); rtMarginWidth.right.Set(CFX_CSSLengthUnit::Point, paraData.GetMarginRight()); rtMarginWidth.bottom.Set(CFX_CSSLengthUnit::Point, paraData.GetSpaceBelow()); pStyle->SetMarginWidth(rtMarginWidth); } CXFA_FontData fontData = pTextProvider->GetFontData(); if (fontData.HasValidNode()) { pStyle->SetColor(fontData.GetColor()); pStyle->SetFontStyle(fontData.IsItalic() ? CFX_CSSFontStyle::Italic : CFX_CSSFontStyle::Normal); pStyle->SetFontWeight(fontData.IsBold() ? FXFONT_FW_BOLD : FXFONT_FW_NORMAL); pStyle->SetNumberVerticalAlign(-fontData.GetBaselineShift()); fFontSize = fontData.GetFontSize(); CFX_CSSLength letterSpacing; letterSpacing.Set(CFX_CSSLengthUnit::Point, fontData.GetLetterSpacing()); pStyle->SetLetterSpacing(letterSpacing); uint32_t dwDecoration = 0; if (fontData.GetLineThrough() > 0) dwDecoration |= CFX_CSSTEXTDECORATION_LineThrough; if (fontData.GetUnderline() > 1) dwDecoration |= CFX_CSSTEXTDECORATION_Double; else if (fontData.GetUnderline() > 0) dwDecoration |= CFX_CSSTEXTDECORATION_Underline; pStyle->SetTextDecoration(dwDecoration); } pStyle->SetLineHeight(fLineHeight); pStyle->SetFontSize(fFontSize); return pStyle; } RetainPtr<CFX_CSSComputedStyle> CXFA_TextParser::CreateStyle( CFX_CSSComputedStyle* pParentStyle) { auto pNewStyle = m_pSelector->CreateComputedStyle(pParentStyle); ASSERT(pNewStyle); if (!pParentStyle) return pNewStyle; uint32_t 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( CFX_XMLNode* pXMLNode, CFX_CSSComputedStyle* pParentStyle) { auto it = m_mapXMLNodeToParseContext.find(pXMLNode); if (it == m_mapXMLNodeToParseContext.end()) return nullptr; CXFA_TextParseContext* pContext = it->second.get(); if (!pContext) return nullptr; pContext->m_pParentStyle.Reset(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(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(CFX_XMLNode* pXMLNode, CFX_CSSComputedStyle* pParentStyle) { if (!pXMLNode) return; auto tagProvider = ParseTagInfo(pXMLNode); if (!tagProvider->m_bTagAvailable) return; RetainPtr<CFX_CSSComputedStyle> pNewStyle; if ((tagProvider->GetTagName() != L"body") || (tagProvider->GetTagName() != L"html")) { auto pTextContext = pdfium::MakeUnique<CXFA_TextParseContext>(); 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->GetNodeItem(CFX_XMLNode::FirstChild); pXMLChild; pXMLChild = pXMLChild->GetNodeItem(CFX_XMLNode::NextSibling)) { 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 }; static const int32_t s_iCount = FX_ArraySize(s_XFATagName); return std::binary_search(s_XFATagName, s_XFATagName + s_iCount, FX_HashCode_GetW(wsName.AsStringView(), true)); } std::unique_ptr<CXFA_TextParser::TagProvider> CXFA_TextParser::ParseTagInfo( CFX_XMLNode* pXMLNode) { auto tagProvider = pdfium::MakeUnique<TagProvider>(); WideString wsName; if (pXMLNode->GetType() == FX_XMLNODE_Element) { CFX_XMLElement* pXMLElement = static_cast<CFX_XMLElement*>(pXMLNode); wsName = pXMLElement->GetLocalTagName(); tagProvider->SetTagName(wsName); tagProvider->m_bTagAvailable = TagValidate(wsName); WideString wsValue = pXMLElement->GetString(L"style"); if (!wsValue.IsEmpty()) tagProvider->SetAttribute(L"style", wsValue); } else if (pXMLNode->GetType() == FX_XMLNODE_Text) { tagProvider->m_bTagAvailable = true; tagProvider->m_bContent = true; } return tagProvider; } XFA_AttributeEnum CXFA_TextParser::GetVAlign( CXFA_TextProvider* pTextProvider) const { CXFA_ParaData paraData = pTextProvider->GetParaData(); return paraData.HasValidNode() ? paraData.GetVerticalAlign() : XFA_AttributeEnum::Top; } float CXFA_TextParser::GetTabInterval(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(CFX_CSSComputedStyle* pStyle) const { WideString wsValue; if (pStyle && pStyle->GetCustomStyle(L"xfa-tab-count", wsValue)) return wsValue.GetInteger(); return 0; } bool CXFA_TextParser::IsSpaceRun(CFX_CSSComputedStyle* pStyle) const { WideString wsValue; if (pStyle && pStyle->GetCustomStyle(L"xfa-spacerun", wsValue)) { wsValue.MakeLower(); return wsValue == L"yes"; } return false; } RetainPtr<CFGAS_GEFont> CXFA_TextParser::GetFont( CXFA_TextProvider* pTextProvider, CFX_CSSComputedStyle* pStyle) const { WideString wsFamily = L"Courier"; uint32_t dwStyle = 0; CXFA_FontData fontData = pTextProvider->GetFontData(); if (fontData.HasValidNode()) { wsFamily = fontData.GetTypeface(); if (fontData.IsBold()) dwStyle |= FXFONT_BOLD; if (fontData.IsItalic()) dwStyle |= FXFONT_BOLD; } if (pStyle) { int32_t iCount = pStyle->CountFontFamilies(); if (iCount > 0) wsFamily = pStyle->GetFontFamily(iCount - 1).AsStringView(); dwStyle = 0; if (pStyle->GetFontWeight() > FXFONT_FW_NORMAL) dwStyle |= FXFONT_BOLD; if (pStyle->GetFontStyle() == CFX_CSSFontStyle::Italic) dwStyle |= FXFONT_ITALIC; } CXFA_FFDoc* pDoc = pTextProvider->GetDocNode(); CXFA_FontMgr* pFontMgr = pDoc->GetApp()->GetXFAFontMgr(); return pFontMgr->GetFont(pDoc, wsFamily.AsStringView(), dwStyle); } float CXFA_TextParser::GetFontSize(CXFA_TextProvider* pTextProvider, CFX_CSSComputedStyle* pStyle) const { if (pStyle) return pStyle->GetFontSize(); CXFA_FontData fontData = pTextProvider->GetFontData(); return fontData.HasValidNode() ? fontData.GetFontSize() : 10; } int32_t CXFA_TextParser::GetHorScale(CXFA_TextProvider* pTextProvider, CFX_CSSComputedStyle* pStyle, 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()) { CXFA_TextParseContext* pContext = it->second.get(); if (pContext && pContext->m_pParentStyle && pContext->m_pParentStyle->GetCustomStyle( L"xfa-font-horizontal-scale", wsValue)) { return wsValue.GetInteger(); } } pXMLNode = pXMLNode->GetNodeItem(CFX_XMLNode::Parent); } } CXFA_FontData fontData = pTextProvider->GetFontData(); return fontData.HasValidNode() ? static_cast<int32_t>(fontData.GetHorizontalScale()) : 100; } int32_t CXFA_TextParser::GetVerScale(CXFA_TextProvider* pTextProvider, CFX_CSSComputedStyle* pStyle) const { if (pStyle) { WideString wsValue; if (pStyle->GetCustomStyle(L"xfa-font-vertical-scale", wsValue)) return wsValue.GetInteger(); } CXFA_FontData fontData = pTextProvider->GetFontData(); return fontData.HasValidNode() ? static_cast<int32_t>(fontData.GetVerticalScale()) : 100; } void CXFA_TextParser::GetUnderline(CXFA_TextProvider* pTextProvider, CFX_CSSComputedStyle* pStyle, int32_t& iUnderline, XFA_AttributeEnum& iPeriod) const { iUnderline = 0; iPeriod = XFA_AttributeEnum::All; CXFA_FontData fontData = pTextProvider->GetFontData(); if (!pStyle) { if (fontData.HasValidNode()) { iUnderline = fontData.GetUnderline(); iPeriod = fontData.GetUnderlinePeriod(); } return; } uint32_t dwDecoration = pStyle->GetTextDecoration(); if (dwDecoration & CFX_CSSTEXTDECORATION_Double) iUnderline = 2; else if (dwDecoration & CFX_CSSTEXTDECORATION_Underline) iUnderline = 1; WideString wsValue; if (pStyle->GetCustomStyle(L"underlinePeriod", wsValue)) { if (wsValue == L"word") iPeriod = XFA_AttributeEnum::Word; } else if (fontData.HasValidNode()) { iPeriod = fontData.GetUnderlinePeriod(); } } void CXFA_TextParser::GetLinethrough(CXFA_TextProvider* pTextProvider, CFX_CSSComputedStyle* pStyle, int32_t& iLinethrough) const { if (pStyle) { uint32_t dwDecoration = pStyle->GetTextDecoration(); iLinethrough = (dwDecoration & CFX_CSSTEXTDECORATION_LineThrough) ? 1 : 0; return; } CXFA_FontData fontData = pTextProvider->GetFontData(); if (fontData.HasValidNode()) iLinethrough = fontData.GetLineThrough(); } FX_ARGB CXFA_TextParser::GetColor(CXFA_TextProvider* pTextProvider, CFX_CSSComputedStyle* pStyle) const { if (pStyle) return pStyle->GetColor(); CXFA_FontData fontData = pTextProvider->GetFontData(); return fontData.HasValidNode() ? fontData.GetColor() : 0xFF000000; } float CXFA_TextParser::GetBaseline(CXFA_TextProvider* pTextProvider, CFX_CSSComputedStyle* pStyle) const { if (pStyle) { if (pStyle->GetVerticalAlign() == CFX_CSSVerticalAlign::Number) return pStyle->GetNumberVerticalAlign(); } else { CXFA_FontData fontData = pTextProvider->GetFontData(); if (fontData.HasValidNode()) return fontData.GetBaselineShift(); } return 0; } float CXFA_TextParser::GetLineHeight(CXFA_TextProvider* pTextProvider, CFX_CSSComputedStyle* pStyle, bool bFirst, float fVerScale) const { float fLineHeight = 0; if (pStyle) { fLineHeight = pStyle->GetLineHeight(); } else { CXFA_ParaData paraData = pTextProvider->GetParaData(); if (paraData.HasValidNode()) fLineHeight = paraData.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; } bool CXFA_TextParser::GetEmbbedObj(CXFA_TextProvider* pTextProvider, CFX_XMLNode* pXMLNode, WideString& wsValue) { wsValue.clear(); if (!pXMLNode) return false; bool bRet = false; if (pXMLNode->GetType() == FX_XMLNODE_Element) { CFX_XMLElement* pElement = static_cast<CFX_XMLElement*>(pXMLNode); WideString wsAttr = pElement->GetString(L"xfa:embed"); if (wsAttr.IsEmpty()) return false; if (wsAttr[0] == L'#') wsAttr.Delete(0); WideString ws = pElement->GetString(L"xfa:embedType"); if (ws.IsEmpty()) ws = L"som"; else ws.MakeLower(); bool bURI = (ws == L"uri"); if (!bURI && ws != L"som") return false; ws = pElement->GetString(L"xfa:embedMode"); if (ws.IsEmpty()) ws = L"formatted"; else ws.MakeLower(); bool bRaw = (ws == L"raw"); if (!bRaw && ws != L"formatted") return false; bRet = pTextProvider->GetEmbbedObj(bURI, bRaw, wsAttr, wsValue); } return bRet; } CXFA_TextParseContext* CXFA_TextParser::GetParseContextFromMap( CFX_XMLNode* pXMLNode) { auto it = m_mapXMLNodeToParseContext.find(pXMLNode); return it != m_mapXMLNodeToParseContext.end() ? it->second.get() : nullptr; } bool CXFA_TextParser::GetTabstops(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; } int32_t iLength = wsValue.GetLength(); const wchar_t* pTabStops = wsValue.c_str(); int32_t iCur = 0; int32_t iLast = 0; WideString wsAlign; TabStopStatus eStatus = TabStopStatus::None; wchar_t ch; while (iCur < iLength) { ch = pTabStops[iCur]; switch (eStatus) { case TabStopStatus::None: if (ch <= ' ') { iCur++; } else { eStatus = TabStopStatus::Alignment; iLast = iCur; } break; case TabStopStatus::Alignment: if (ch == ' ') { wsAlign = WideStringView(pTabStops + iLast, iCur - iLast); eStatus = TabStopStatus::StartLeader; iCur++; while (iCur < iLength && pTabStops[iCur] <= ' ') iCur++; iLast = iCur; } else { iCur++; } break; case TabStopStatus::StartLeader: if (ch != 'l') { eStatus = TabStopStatus::Location; } else { int32_t iCount = 0; while (iCur < iLength) { ch = pTabStops[iCur]; iCur++; if (ch == '(') { iCount++; } else if (ch == ')') { iCount--; if (iCount == 0) break; } } while (iCur < iLength && pTabStops[iCur] <= ' ') iCur++; iLast = iCur; eStatus = TabStopStatus::Location; } break; case TabStopStatus::Location: if (ch == ' ') { uint32_t dwHashCode = FX_HashCode_GetW(wsAlign.AsStringView(), true); CXFA_Measurement ms(WideStringView(pTabStops + 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_GetW(wsAlign.AsStringView(), true); CXFA_Measurement ms(WideStringView(pTabStops + iLast, iCur - iLast)); float fPos = ms.ToUnit(XFA_Unit::Pt); pTabstopContext->Append(dwHashCode, fPos); } return true; } CXFA_TextParser::TagProvider::TagProvider() : m_bTagAvailable(false), m_bContent(false) {} CXFA_TextParser::TagProvider::~TagProvider() {}