You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1258 lines
40 KiB
1258 lines
40 KiB
// 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_textlayout.h"
|
|
|
|
#include <algorithm>
|
|
#include <utility>
|
|
|
|
#include "core/fxcrt/css/cfx_csscomputedstyle.h"
|
|
#include "core/fxcrt/css/cfx_cssstyleselector.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_graphstatedata.h"
|
|
#include "core/fxge/cfx_pathdata.h"
|
|
#include "core/fxge/cfx_renderdevice.h"
|
|
#include "core/fxge/text_char_pos.h"
|
|
#include "fxjs/xfa/cjx_object.h"
|
|
#include "third_party/base/ptr_util.h"
|
|
#include "third_party/base/stl_util.h"
|
|
#include "xfa/fde/cfde_textout.h"
|
|
#include "xfa/fgas/font/cfgas_gefont.h"
|
|
#include "xfa/fgas/layout/cfx_linkuserdata.h"
|
|
#include "xfa/fgas/layout/cfx_rtfbreak.h"
|
|
#include "xfa/fgas/layout/cfx_textuserdata.h"
|
|
#include "xfa/fxfa/cxfa_loadercontext.h"
|
|
#include "xfa/fxfa/cxfa_pieceline.h"
|
|
#include "xfa/fxfa/cxfa_textparsecontext.h"
|
|
#include "xfa/fxfa/cxfa_textpiece.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) {
|
|
int32_t iLen = pText->GetLength();
|
|
if (iLen == 0)
|
|
return;
|
|
|
|
int32_t iTrimLeft = 0;
|
|
{
|
|
// Span's lifetime must end before ReleaseBuffer() below.
|
|
pdfium::span<wchar_t> psz = pText->GetBuffer(iLen);
|
|
wchar_t wPrev = 0;
|
|
for (int32_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[iTrimLeft++] = wch;
|
|
}
|
|
}
|
|
pText->ReleaseBuffer(iTrimLeft);
|
|
}
|
|
|
|
} // namespace
|
|
|
|
CXFA_TextLayout::CXFA_TextLayout(CXFA_FFDoc* doc,
|
|
CXFA_TextProvider* pTextProvider)
|
|
: m_pDoc(doc), m_pTextProvider(pTextProvider) {
|
|
ASSERT(m_pTextProvider);
|
|
}
|
|
|
|
CXFA_TextLayout::~CXFA_TextLayout() {
|
|
m_textParser.Reset();
|
|
Unload();
|
|
}
|
|
|
|
void CXFA_TextLayout::Unload() {
|
|
m_pieceLines.clear();
|
|
m_pBreak.reset();
|
|
}
|
|
|
|
void CXFA_TextLayout::GetTextDataNode() {
|
|
CXFA_Node* pNode = m_pTextProvider->GetTextNode(&m_bRichText);
|
|
if (pNode && m_bRichText)
|
|
m_textParser.Reset();
|
|
|
|
m_pTextDataNode = pNode;
|
|
}
|
|
|
|
CFX_XMLNode* CXFA_TextLayout::GetXMLContainerNode() {
|
|
if (!m_bRichText)
|
|
return nullptr;
|
|
|
|
CFX_XMLNode* pXMLRoot = m_pTextDataNode->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<CFX_RTFBreak> CXFA_TextLayout::CreateBreak(bool bDefault) {
|
|
uint32_t dwStyle = FX_LAYOUTSTYLE_ExpandTab;
|
|
if (!bDefault)
|
|
dwStyle |= FX_LAYOUTSTYLE_Pagination;
|
|
|
|
auto pBreak = pdfium::MakeUnique<CFX_RTFBreak>(dwStyle);
|
|
pBreak->SetLineBreakTolerance(1);
|
|
pBreak->SetFont(m_textParser.GetFont(m_pDoc.Get(), m_pTextProvider, nullptr));
|
|
pBreak->SetFontSize(m_textParser.GetFontSize(m_pTextProvider, nullptr));
|
|
return pBreak;
|
|
}
|
|
|
|
void CXFA_TextLayout::InitBreak(float fLineWidth) {
|
|
CXFA_Para* para = m_pTextProvider->GetParaIfExists();
|
|
float fStart = 0;
|
|
float fStartPos = 0;
|
|
if (para) {
|
|
CFX_RTFLineAlignment iAlign = CFX_RTFLineAlignment::Left;
|
|
switch (para->GetHorizontalAlign()) {
|
|
case XFA_AttributeValue::Center:
|
|
iAlign = CFX_RTFLineAlignment::Center;
|
|
break;
|
|
case XFA_AttributeValue::Right:
|
|
iAlign = CFX_RTFLineAlignment::Right;
|
|
break;
|
|
case XFA_AttributeValue::Justify:
|
|
iAlign = CFX_RTFLineAlignment::Justified;
|
|
break;
|
|
case XFA_AttributeValue::JustifyAll:
|
|
iAlign = CFX_RTFLineAlignment::Distributed;
|
|
break;
|
|
case XFA_AttributeValue::Left:
|
|
case XFA_AttributeValue::Radix:
|
|
break;
|
|
default:
|
|
NOTREACHED();
|
|
break;
|
|
}
|
|
m_pBreak->SetAlignment(iAlign);
|
|
|
|
fStart = para->GetMarginLeft();
|
|
if (m_pTextProvider->IsCheckButtonAndAutoWidth()) {
|
|
if (iAlign != CFX_RTFLineAlignment::Left)
|
|
fLineWidth -= para->GetMarginRight();
|
|
} else {
|
|
fLineWidth -= para->GetMarginRight();
|
|
}
|
|
if (fLineWidth < 0)
|
|
fLineWidth = fStart;
|
|
|
|
fStartPos = fStart;
|
|
float fIndent = para->GetTextIndent();
|
|
if (fIndent > 0)
|
|
fStartPos += fIndent;
|
|
}
|
|
|
|
m_pBreak->SetLineBoundary(fStart, fLineWidth);
|
|
m_pBreak->SetLineStartPos(fStartPos);
|
|
|
|
CXFA_Font* font = m_pTextProvider->GetFontIfExists();
|
|
if (font) {
|
|
m_pBreak->SetHorizontalScale(
|
|
static_cast<int32_t>(font->GetHorizontalScale()));
|
|
m_pBreak->SetVerticalScale(static_cast<int32_t>(font->GetVerticalScale()));
|
|
m_pBreak->SetCharSpace(font->GetLetterSpacing());
|
|
}
|
|
|
|
float fFontSize = m_textParser.GetFontSize(m_pTextProvider, nullptr);
|
|
m_pBreak->SetFontSize(fFontSize);
|
|
m_pBreak->SetFont(
|
|
m_textParser.GetFont(m_pDoc.Get(), m_pTextProvider, nullptr));
|
|
m_pBreak->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) {
|
|
CFX_RTFLineAlignment iAlign = CFX_RTFLineAlignment::Left;
|
|
switch (pStyle->GetTextAlign()) {
|
|
case CFX_CSSTextAlign::Right:
|
|
iAlign = CFX_RTFLineAlignment::Right;
|
|
break;
|
|
case CFX_CSSTextAlign::Center:
|
|
iAlign = CFX_RTFLineAlignment::Center;
|
|
break;
|
|
case CFX_CSSTextAlign::Justify:
|
|
iAlign = CFX_RTFLineAlignment::Justified;
|
|
break;
|
|
case CFX_CSSTextAlign::JustifyAll:
|
|
iAlign = CFX_RTFLineAlignment::Distributed;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
m_pBreak->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);
|
|
}
|
|
}
|
|
m_pBreak->SetLineBoundary(fStart, fLineWidth);
|
|
float fIndent = pStyle->GetTextIndent().GetValue();
|
|
if (fIndent > 0)
|
|
fStart += fIndent;
|
|
|
|
m_pBreak->SetLineStartPos(fStart);
|
|
m_pBreak->SetTabWidth(m_textParser.GetTabInterval(pStyle));
|
|
if (!m_pTabstopContext)
|
|
m_pTabstopContext = pdfium::MakeUnique<CXFA_TextTabstopsContext>();
|
|
m_textParser.GetTabstops(pStyle, m_pTabstopContext.get());
|
|
for (const auto& stop : m_pTabstopContext->m_tabstops)
|
|
m_pBreak->AddPositionedTab(stop.fTabstops);
|
|
}
|
|
float fFontSize = m_textParser.GetFontSize(m_pTextProvider, pStyle);
|
|
m_pBreak->SetFontSize(fFontSize);
|
|
m_pBreak->SetLineBreakTolerance(fFontSize * 0.2f);
|
|
m_pBreak->SetFont(
|
|
m_textParser.GetFont(m_pDoc.Get(), m_pTextProvider, pStyle));
|
|
m_pBreak->SetHorizontalScale(
|
|
m_textParser.GetHorScale(m_pTextProvider, pStyle, pXMLNode));
|
|
m_pBreak->SetVerticalScale(m_textParser.GetVerScale(m_pTextProvider, pStyle));
|
|
m_pBreak->SetCharSpace(pStyle->GetLetterSpacing().GetValue());
|
|
}
|
|
|
|
float CXFA_TextLayout::GetLayoutHeight() {
|
|
if (!m_pLoader)
|
|
return 0;
|
|
|
|
if (m_pLoader->lineHeights.empty() && m_pLoader->fWidth > 0) {
|
|
CFX_SizeF szMax(m_pLoader->fWidth, m_pLoader->fHeight);
|
|
m_pLoader->bSaveLineHeight = true;
|
|
m_pLoader->fLastPos = 0;
|
|
CFX_SizeF szDef = CalcSize(szMax, szMax);
|
|
m_pLoader->bSaveLineHeight = false;
|
|
return szDef.height;
|
|
}
|
|
|
|
float fHeight = m_pLoader->fHeight;
|
|
if (fHeight < 0.1f) {
|
|
fHeight = 0;
|
|
for (float value : m_pLoader->lineHeights)
|
|
fHeight += value;
|
|
}
|
|
return fHeight;
|
|
}
|
|
|
|
float CXFA_TextLayout::StartLayout(float fWidth) {
|
|
if (!m_pLoader)
|
|
m_pLoader = pdfium::MakeUnique<CXFA_LoaderContext>();
|
|
|
|
if (fWidth < 0 ||
|
|
(m_pLoader->fWidth > -1 && fabs(fWidth - m_pLoader->fWidth) > 0)) {
|
|
m_pLoader->lineHeights.clear();
|
|
m_Blocks.clear();
|
|
Unload();
|
|
m_pLoader->fStartLineOffset = 0;
|
|
}
|
|
m_pLoader->fWidth = fWidth;
|
|
|
|
if (fWidth >= 0)
|
|
return fWidth;
|
|
|
|
CFX_SizeF szMax;
|
|
|
|
m_pLoader->bSaveLineHeight = true;
|
|
m_pLoader->fLastPos = 0;
|
|
CFX_SizeF szDef = CalcSize(szMax, szMax);
|
|
m_pLoader->bSaveLineHeight = false;
|
|
return szDef.width;
|
|
}
|
|
|
|
float CXFA_TextLayout::DoLayout(float fTextHeight) {
|
|
if (!m_pLoader)
|
|
return fTextHeight;
|
|
|
|
UpdateLoaderHeight(fTextHeight);
|
|
return fTextHeight;
|
|
}
|
|
|
|
float CXFA_TextLayout::DoSplitLayout(size_t szBlockIndex,
|
|
float fCalcHeight,
|
|
float fTextHeight) {
|
|
if (!m_pLoader)
|
|
return fCalcHeight;
|
|
|
|
UpdateLoaderHeight(fTextHeight);
|
|
|
|
if (fCalcHeight < 0)
|
|
return fCalcHeight;
|
|
|
|
m_bHasBlock = true;
|
|
if (m_Blocks.empty() && m_pLoader->fHeight > 0) {
|
|
float fHeight = fTextHeight - GetLayoutHeight();
|
|
if (fHeight > 0) {
|
|
XFA_AttributeValue iAlign = m_textParser.GetVAlign(m_pTextProvider);
|
|
if (iAlign == XFA_AttributeValue::Middle)
|
|
fHeight /= 2.0f;
|
|
else if (iAlign != XFA_AttributeValue::Bottom)
|
|
fHeight = 0;
|
|
m_pLoader->fStartLineOffset = fHeight;
|
|
}
|
|
}
|
|
|
|
float fLinePos = m_pLoader->fStartLineOffset;
|
|
size_t szLineIndex = 0;
|
|
if (!m_Blocks.empty()) {
|
|
if (szBlockIndex < m_Blocks.size())
|
|
szLineIndex = m_Blocks[szBlockIndex].szIndex;
|
|
else
|
|
szLineIndex = GetNextIndexFromLastBlockData();
|
|
for (size_t i = 0;
|
|
i < std::min(szBlockIndex, m_pLoader->blockHeights.size()); ++i) {
|
|
fLinePos -= m_pLoader->blockHeights[i].fHeight;
|
|
}
|
|
}
|
|
|
|
if (szLineIndex >= m_pLoader->lineHeights.size())
|
|
return fCalcHeight;
|
|
|
|
if (m_pLoader->lineHeights[szLineIndex] - fCalcHeight > kHeightTolerance)
|
|
return 0;
|
|
|
|
for (size_t i = szLineIndex; i < m_pLoader->lineHeights.size(); ++i) {
|
|
float fLineHeight = m_pLoader->lineHeights[i];
|
|
if (fLinePos + fLineHeight - fCalcHeight <= kHeightTolerance) {
|
|
fLinePos += fLineHeight;
|
|
continue;
|
|
}
|
|
|
|
if (szBlockIndex < m_Blocks.size())
|
|
m_Blocks[szBlockIndex] = {szLineIndex, i - szLineIndex};
|
|
else
|
|
m_Blocks.push_back({szLineIndex, i - szLineIndex});
|
|
|
|
if (i != szLineIndex)
|
|
return fLinePos;
|
|
|
|
if (fCalcHeight > fLinePos)
|
|
return fCalcHeight;
|
|
|
|
if (szBlockIndex < m_pLoader->blockHeights.size() &&
|
|
m_pLoader->blockHeights[szBlockIndex].szBlockIndex == szBlockIndex) {
|
|
m_pLoader->blockHeights[szBlockIndex].fHeight = fCalcHeight;
|
|
} else {
|
|
m_pLoader->blockHeights.push_back({szBlockIndex, fCalcHeight});
|
|
}
|
|
return fCalcHeight;
|
|
}
|
|
return fCalcHeight;
|
|
}
|
|
|
|
size_t CXFA_TextLayout::CountBlocks() const {
|
|
size_t szCount = m_Blocks.size();
|
|
return szCount > 0 ? szCount : 1;
|
|
}
|
|
|
|
size_t CXFA_TextLayout::GetNextIndexFromLastBlockData() const {
|
|
return m_Blocks.back().szIndex + m_Blocks.back().szLength;
|
|
}
|
|
|
|
void CXFA_TextLayout::UpdateLoaderHeight(float fTextHeight) {
|
|
m_pLoader->fHeight = fTextHeight;
|
|
if (m_pLoader->fHeight < 0)
|
|
m_pLoader->fHeight = GetLayoutHeight();
|
|
}
|
|
|
|
CFX_SizeF CXFA_TextLayout::CalcSize(const CFX_SizeF& minSize,
|
|
const CFX_SizeF& maxSize) {
|
|
float width = maxSize.width;
|
|
if (width < 1)
|
|
width = 0xFFFF;
|
|
|
|
m_pBreak = CreateBreak(false);
|
|
float fLinePos = 0;
|
|
m_iLines = 0;
|
|
m_fMaxWidth = 0;
|
|
Loader(width, &fLinePos, false);
|
|
if (fLinePos < 0.1f)
|
|
fLinePos = m_textParser.GetFontSize(m_pTextProvider, nullptr);
|
|
|
|
m_pTabstopContext.reset();
|
|
return CFX_SizeF(m_fMaxWidth, fLinePos);
|
|
}
|
|
|
|
float CXFA_TextLayout::Layout(const CFX_SizeF& size) {
|
|
if (size.width < 1)
|
|
return 0.f;
|
|
|
|
Unload();
|
|
m_pBreak = CreateBreak(true);
|
|
if (m_pLoader) {
|
|
m_pLoader->iTotalLines = -1;
|
|
m_pLoader->iChar = 0;
|
|
}
|
|
|
|
m_iLines = 0;
|
|
float fLinePos = 0;
|
|
Loader(size.width, &fLinePos, true);
|
|
UpdateAlign(size.height, fLinePos);
|
|
m_pTabstopContext.reset();
|
|
return fLinePos;
|
|
}
|
|
|
|
bool CXFA_TextLayout::LayoutInternal(size_t szBlockIndex) {
|
|
ASSERT(szBlockIndex < CountBlocks());
|
|
|
|
if (!m_pLoader || m_pLoader->fWidth < 1)
|
|
return false;
|
|
|
|
m_pLoader->iTotalLines = -1;
|
|
m_iLines = 0;
|
|
float fLinePos = 0;
|
|
CXFA_Node* pNode = nullptr;
|
|
CFX_SizeF szText(m_pLoader->fWidth, m_pLoader->fHeight);
|
|
if (szBlockIndex < m_pLoader->blockHeights.size())
|
|
return true;
|
|
if (szBlockIndex == m_pLoader->blockHeights.size()) {
|
|
Unload();
|
|
m_pBreak = CreateBreak(true);
|
|
fLinePos = m_pLoader->fStartLineOffset;
|
|
for (size_t i = 0; i < m_pLoader->blockHeights.size(); ++i)
|
|
fLinePos -= m_pLoader->blockHeights[i].fHeight;
|
|
|
|
m_pLoader->iChar = 0;
|
|
if (!m_Blocks.empty())
|
|
m_pLoader->iTotalLines = m_Blocks[szBlockIndex].szLength;
|
|
|
|
Loader(szText.width, &fLinePos, true);
|
|
if (m_Blocks.empty() && m_pLoader->fStartLineOffset < 0.1f)
|
|
UpdateAlign(szText.height, fLinePos);
|
|
} else if (m_pTextDataNode) {
|
|
if (!m_Blocks.empty() && szBlockIndex < m_Blocks.size() - 1)
|
|
m_pLoader->iTotalLines = m_Blocks[szBlockIndex].szLength;
|
|
|
|
m_pBreak->Reset();
|
|
if (m_bRichText) {
|
|
CFX_XMLNode* pContainerNode = GetXMLContainerNode();
|
|
if (!pContainerNode)
|
|
return true;
|
|
|
|
const CFX_XMLNode* pXMLNode = m_pLoader->pXMLNode.Get();
|
|
if (!pXMLNode)
|
|
return true;
|
|
|
|
const CFX_XMLNode* pSaveXMLNode = pXMLNode;
|
|
for (; pXMLNode; pXMLNode = pXMLNode->GetNextSibling()) {
|
|
if (!LoadRichText(pXMLNode, szText.width, &fLinePos,
|
|
m_pLoader->pParentStyle, true, nullptr, true, false,
|
|
0)) {
|
|
break;
|
|
}
|
|
}
|
|
while (!pXMLNode) {
|
|
pXMLNode = pSaveXMLNode->GetParent();
|
|
if (pXMLNode == pContainerNode)
|
|
break;
|
|
if (!LoadRichText(pXMLNode, szText.width, &fLinePos,
|
|
m_pLoader->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,
|
|
m_pLoader->pParentStyle, true, nullptr, true, false,
|
|
0)) {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
pNode = m_pLoader->pNode.Get();
|
|
if (!pNode)
|
|
return true;
|
|
LoadText(pNode, szText.width, &fLinePos, true);
|
|
}
|
|
}
|
|
if (szBlockIndex == m_Blocks.size()) {
|
|
m_pTabstopContext.reset();
|
|
m_pLoader.reset();
|
|
}
|
|
return true;
|
|
}
|
|
|
|
void CXFA_TextLayout::ItemBlocks(const CFX_RectF& rtText, size_t szBlockIndex) {
|
|
if (!m_pLoader)
|
|
return;
|
|
|
|
if (m_pLoader->lineHeights.empty())
|
|
return;
|
|
|
|
float fLinePos = m_pLoader->fStartLineOffset;
|
|
size_t szLineIndex = 0;
|
|
if (szBlockIndex > 0) {
|
|
if (szBlockIndex <= m_pLoader->blockHeights.size()) {
|
|
for (size_t i = 0; i < szBlockIndex; ++i)
|
|
fLinePos -= m_pLoader->blockHeights[i].fHeight;
|
|
} else {
|
|
fLinePos = 0;
|
|
}
|
|
szLineIndex = GetNextIndexFromLastBlockData();
|
|
}
|
|
|
|
size_t i;
|
|
for (i = szLineIndex; i < m_pLoader->lineHeights.size(); ++i) {
|
|
float fLineHeight = m_pLoader->lineHeights[i];
|
|
if (fLinePos + fLineHeight - rtText.height > kHeightTolerance) {
|
|
m_Blocks.push_back({szLineIndex, i - szLineIndex});
|
|
return;
|
|
}
|
|
fLinePos += fLineHeight;
|
|
}
|
|
if (i > szLineIndex)
|
|
m_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 (m_pieceLines.empty()) {
|
|
size_t szBlockCount = CountBlocks();
|
|
for (size_t i = 0; i < szBlockCount; ++i)
|
|
LayoutInternal(i);
|
|
}
|
|
|
|
std::vector<TextCharPos> char_pos(1);
|
|
size_t szLineStart = 0;
|
|
size_t szPieceLines = m_pieceLines.size();
|
|
if (!m_Blocks.empty()) {
|
|
if (szBlockIndex < m_Blocks.size()) {
|
|
szLineStart = m_Blocks[szBlockIndex].szIndex;
|
|
szPieceLines = m_Blocks[szBlockIndex].szLength;
|
|
} else {
|
|
szPieceLines = 0;
|
|
}
|
|
}
|
|
|
|
for (size_t i = 0; i < szPieceLines; ++i) {
|
|
if (i + szLineStart >= m_pieceLines.size())
|
|
break;
|
|
|
|
CXFA_PieceLine* pPieceLine = m_pieceLines[i + szLineStart].get();
|
|
for (size_t j = 0; j < pPieceLine->m_textPieces.size(); ++j) {
|
|
const CXFA_TextPiece* pPiece = pPieceLine->m_textPieces[j].get();
|
|
int32_t iChars = pPiece->iChars;
|
|
if (pdfium::CollectionSize<int32_t>(char_pos) < iChars)
|
|
char_pos.resize(iChars);
|
|
RenderString(pFxDevice, pPieceLine, j, &char_pos, mtDoc2Device);
|
|
}
|
|
for (size_t j = 0; j < pPieceLine->m_textPieces.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 (m_textParser.GetVAlign(m_pTextProvider)) {
|
|
case XFA_AttributeValue::Middle:
|
|
fHeight /= 2.0f;
|
|
break;
|
|
case XFA_AttributeValue::Bottom:
|
|
break;
|
|
default:
|
|
return;
|
|
}
|
|
|
|
for (const auto& pPieceLine : m_pieceLines) {
|
|
for (const auto& pPiece : pPieceLine->m_textPieces)
|
|
pPiece->rtPiece.top += fHeight;
|
|
}
|
|
}
|
|
|
|
void CXFA_TextLayout::Loader(float textWidth,
|
|
float* pLinePos,
|
|
bool bSavePieces) {
|
|
GetTextDataNode();
|
|
if (!m_pTextDataNode)
|
|
return;
|
|
|
|
if (!m_bRichText) {
|
|
LoadText(m_pTextDataNode, textWidth, pLinePos, bSavePieces);
|
|
return;
|
|
}
|
|
|
|
const CFX_XMLNode* pXMLContainer = GetXMLContainerNode();
|
|
if (!pXMLContainer)
|
|
return;
|
|
|
|
if (!m_textParser.IsParsed())
|
|
m_textParser.DoParse(pXMLContainer, m_pTextProvider);
|
|
|
|
auto pRootStyle = m_textParser.CreateRootStyle(m_pTextProvider);
|
|
LoadRichText(pXMLContainer, textWidth, pLinePos, pRootStyle, bSavePieces,
|
|
nullptr, true, false, 0);
|
|
}
|
|
|
|
void CXFA_TextLayout::LoadText(CXFA_Node* pNode,
|
|
float textWidth,
|
|
float* pLinePos,
|
|
bool bSavePieces) {
|
|
InitBreak(textWidth);
|
|
|
|
CXFA_Para* para = m_pTextProvider->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();
|
|
break;
|
|
}
|
|
}
|
|
|
|
WideString wsText = pNode->JSObject()->GetContent(false);
|
|
wsText.TrimRight(L" ");
|
|
bool bRet = AppendChar(wsText, pLinePos, fSpaceAbove, bSavePieces);
|
|
if (bRet && m_pLoader)
|
|
m_pLoader->pNode = pNode;
|
|
else
|
|
EndBreak(CFX_BreakType::Paragraph, pLinePos, bSavePieces);
|
|
}
|
|
|
|
bool CXFA_TextLayout::LoadRichText(
|
|
const CFX_XMLNode* pXMLNode,
|
|
float textWidth,
|
|
float* pLinePos,
|
|
const RetainPtr<CFX_CSSComputedStyle>& pParentStyle,
|
|
bool bSavePieces,
|
|
RetainPtr<CFX_LinkUserData> pLinkData,
|
|
bool bEndBreak,
|
|
bool bIsOl,
|
|
int32_t iLiCount) {
|
|
if (!pXMLNode)
|
|
return false;
|
|
|
|
CXFA_TextParseContext* pContext =
|
|
m_textParser.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 (m_bBlockContinue || (m_pLoader && pXMLNode == m_pLoader->pXMLNode)) {
|
|
m_bBlockContinue = true;
|
|
}
|
|
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;
|
|
}
|
|
if (m_bBlockContinue || !bContentNode) {
|
|
eDisplay = pContext->GetDisplay();
|
|
if (eDisplay != CFX_CSSDisplay::Block &&
|
|
eDisplay != CFX_CSSDisplay::Inline &&
|
|
eDisplay != CFX_CSSDisplay::ListItem) {
|
|
return true;
|
|
}
|
|
|
|
pStyle = m_textParser.ComputeStyle(pXMLNode, pParentStyle.Get());
|
|
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<CFX_LinkUserData>(wsLinkContent);
|
|
}
|
|
|
|
int32_t iTabCount = m_textParser.CountTabs(
|
|
bContentNode ? pParentStyle.Get() : pStyle.Get());
|
|
bool bSpaceRun = m_textParser.IsSpaceRun(
|
|
bContentNode ? pParentStyle.Get() : pStyle.Get());
|
|
WideString wsText;
|
|
if (bContentNode && iTabCount == 0) {
|
|
wsText = ToXMLText(pXMLNode)->GetText();
|
|
} else if (wsName.EqualsASCII("br")) {
|
|
wsText = L'\n';
|
|
} else if (wsName.EqualsASCII("li")) {
|
|
bCurLi = true;
|
|
if (bIsOl)
|
|
wsText = WideString::Format(L"%d. ", iLiCount);
|
|
else
|
|
wsText = 0x00B7 + WideStringView(L" ", 1);
|
|
} else if (!bContentNode) {
|
|
if (iTabCount > 0) {
|
|
while (iTabCount-- > 0)
|
|
wsText += L'\t';
|
|
} else {
|
|
Optional<WideString> obj =
|
|
m_textParser.GetEmbeddedObj(m_pTextProvider, pXMLNode);
|
|
if (obj)
|
|
wsText = *obj;
|
|
}
|
|
}
|
|
|
|
int32_t iLength = wsText.GetLength();
|
|
if (iLength > 0 && bContentNode && !bSpaceRun)
|
|
ProcessText(&wsText);
|
|
|
|
if (m_pLoader) {
|
|
if (wsText.GetLength() > 0 && m_pLoader->bFilterSpace) {
|
|
wsText.TrimLeft(L" ");
|
|
}
|
|
if (CFX_CSSDisplay::Block == eDisplay) {
|
|
m_pLoader->bFilterSpace = true;
|
|
} else if (CFX_CSSDisplay::Inline == eDisplay &&
|
|
m_pLoader->bFilterSpace) {
|
|
m_pLoader->bFilterSpace = false;
|
|
} else if (wsText.GetLength() > 0 && wsText.Back() == 0x20) {
|
|
m_pLoader->bFilterSpace = true;
|
|
} else if (wsText.GetLength() != 0) {
|
|
m_pLoader->bFilterSpace = false;
|
|
}
|
|
}
|
|
|
|
if (wsText.GetLength() > 0) {
|
|
if (!m_pLoader || m_pLoader->iChar == 0) {
|
|
auto pUserData = pdfium::MakeRetain<CFX_TextUserData>(
|
|
bContentNode ? pParentStyle : pStyle, pLinkData);
|
|
m_pBreak->SetUserData(pUserData);
|
|
}
|
|
|
|
if (AppendChar(wsText, pLinePos, 0, bSavePieces)) {
|
|
if (m_pLoader)
|
|
m_pLoader->bFilterSpace = false;
|
|
if (!IsEnd(bSavePieces))
|
|
return true;
|
|
if (m_pLoader && m_pLoader->iTotalLines > -1) {
|
|
m_pLoader->pXMLNode = pXMLNode;
|
|
m_pLoader->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 (m_pLoader) {
|
|
if (CFX_CSSDisplay::Block == eDisplay)
|
|
m_pLoader->bFilterSpace = true;
|
|
}
|
|
if (bCurLi)
|
|
EndBreak(CFX_BreakType::Line, pLinePos, bSavePieces);
|
|
} else {
|
|
if (pContext)
|
|
eDisplay = pContext->GetDisplay();
|
|
}
|
|
|
|
if (m_bBlockContinue) {
|
|
if (pContext && !bContentNode) {
|
|
CFX_BreakType dwStatus = (eDisplay == CFX_CSSDisplay::Block)
|
|
? CFX_BreakType::Paragraph
|
|
: CFX_BreakType::Piece;
|
|
EndBreak(dwStatus, pLinePos, bSavePieces);
|
|
if (eDisplay == CFX_CSSDisplay::Block) {
|
|
*pLinePos += fSpaceBelow;
|
|
if (m_pTabstopContext)
|
|
m_pTabstopContext->RemoveAll();
|
|
}
|
|
if (IsEnd(bSavePieces)) {
|
|
if (m_pLoader && m_pLoader->iTotalLines > -1) {
|
|
m_pLoader->pXMLNode = pXMLNode->GetNextSibling();
|
|
m_pLoader->pParentStyle = pParentStyle;
|
|
}
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool CXFA_TextLayout::AppendChar(const WideString& wsText,
|
|
float* pLinePos,
|
|
float fSpaceAbove,
|
|
bool bSavePieces) {
|
|
CFX_BreakType dwStatus = CFX_BreakType::None;
|
|
int32_t iChar = 0;
|
|
if (m_pLoader)
|
|
iChar = m_pLoader->iChar;
|
|
|
|
int32_t iLength = wsText.GetLength();
|
|
for (int32_t i = iChar; i < iLength; i++) {
|
|
wchar_t wch = wsText[i];
|
|
if (wch == 0xA0)
|
|
wch = 0x20;
|
|
|
|
dwStatus = m_pBreak->AppendChar(wch);
|
|
if (dwStatus != CFX_BreakType::None && dwStatus != CFX_BreakType::Piece) {
|
|
AppendTextLine(dwStatus, pLinePos, bSavePieces, false);
|
|
if (IsEnd(bSavePieces)) {
|
|
if (m_pLoader)
|
|
m_pLoader->iChar = i;
|
|
return true;
|
|
}
|
|
if (dwStatus == CFX_BreakType::Paragraph && m_bRichText)
|
|
*pLinePos += fSpaceAbove;
|
|
}
|
|
}
|
|
if (m_pLoader)
|
|
m_pLoader->iChar = 0;
|
|
|
|
return false;
|
|
}
|
|
|
|
bool CXFA_TextLayout::IsEnd(bool bSavePieces) {
|
|
if (!bSavePieces)
|
|
return false;
|
|
if (m_pLoader && m_pLoader->iTotalLines > 0)
|
|
return m_iLines >= m_pLoader->iTotalLines;
|
|
return false;
|
|
}
|
|
|
|
void CXFA_TextLayout::EndBreak(CFX_BreakType dwStatus,
|
|
float* pLinePos,
|
|
bool bSavePieces) {
|
|
dwStatus = m_pBreak->EndBreak(dwStatus);
|
|
if (dwStatus != CFX_BreakType::None && dwStatus != CFX_BreakType::Piece)
|
|
AppendTextLine(dwStatus, pLinePos, bSavePieces, true);
|
|
}
|
|
|
|
void CXFA_TextLayout::DoTabstops(CFX_CSSComputedStyle* pStyle,
|
|
CXFA_PieceLine* pPieceLine) {
|
|
if (!pStyle || !pPieceLine)
|
|
return;
|
|
|
|
if (!m_pTabstopContext || m_pTabstopContext->m_tabstops.empty())
|
|
return;
|
|
|
|
int32_t iPieces = pdfium::CollectionSize<int32_t>(pPieceLine->m_textPieces);
|
|
if (iPieces == 0)
|
|
return;
|
|
|
|
CXFA_TextPiece* pPiece = pPieceLine->m_textPieces[iPieces - 1].get();
|
|
int32_t& iTabstopsIndex = m_pTabstopContext->m_iTabIndex;
|
|
int32_t iCount = m_textParser.CountTabs(pStyle);
|
|
if (!pdfium::IndexInBounds(m_pTabstopContext->m_tabstops, iTabstopsIndex))
|
|
return;
|
|
|
|
if (iCount > 0) {
|
|
iTabstopsIndex++;
|
|
m_pTabstopContext->m_bTabstops = true;
|
|
float fRight = 0;
|
|
if (iPieces > 1) {
|
|
CXFA_TextPiece* p = pPieceLine->m_textPieces[iPieces - 2].get();
|
|
fRight = p->rtPiece.right();
|
|
}
|
|
m_pTabstopContext->m_fTabWidth =
|
|
pPiece->rtPiece.width + pPiece->rtPiece.left - fRight;
|
|
} else if (iTabstopsIndex > -1) {
|
|
float fLeft = 0;
|
|
if (m_pTabstopContext->m_bTabstops) {
|
|
uint32_t dwAlign = m_pTabstopContext->m_tabstops[iTabstopsIndex].dwAlign;
|
|
if (dwAlign == FX_HashCode_GetW(L"center", false)) {
|
|
fLeft = pPiece->rtPiece.width / 2.0f;
|
|
} else if (dwAlign == FX_HashCode_GetW(L"right", false) ||
|
|
dwAlign == FX_HashCode_GetW(L"before", false)) {
|
|
fLeft = pPiece->rtPiece.width;
|
|
} else if (dwAlign == FX_HashCode_GetW(L"decimal", false)) {
|
|
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;
|
|
}
|
|
}
|
|
m_pTabstopContext->m_fLeft =
|
|
std::min(fLeft, m_pTabstopContext->m_fTabWidth);
|
|
m_pTabstopContext->m_bTabstops = false;
|
|
m_pTabstopContext->m_fTabWidth = 0;
|
|
}
|
|
pPiece->rtPiece.left -= m_pTabstopContext->m_fLeft;
|
|
}
|
|
}
|
|
|
|
void CXFA_TextLayout::AppendTextLine(CFX_BreakType dwStatus,
|
|
float* pLinePos,
|
|
bool bSavePieces,
|
|
bool bEndBreak) {
|
|
int32_t iPieces = m_pBreak->CountBreakPieces();
|
|
if (iPieces < 1)
|
|
return;
|
|
|
|
RetainPtr<CFX_CSSComputedStyle> pStyle;
|
|
if (bSavePieces) {
|
|
auto pNew = pdfium::MakeUnique<CXFA_PieceLine>();
|
|
CXFA_PieceLine* pPieceLine = pNew.get();
|
|
m_pieceLines.push_back(std::move(pNew));
|
|
if (m_pTabstopContext)
|
|
m_pTabstopContext->Reset();
|
|
|
|
float fLineStep = 0, fBaseLine = 0;
|
|
int32_t i = 0;
|
|
for (i = 0; i < iPieces; i++) {
|
|
const CFX_BreakPiece* pPiece = m_pBreak->GetBreakPieceUnstable(i);
|
|
CFX_TextUserData* pUserData = pPiece->m_pUserData.Get();
|
|
if (pUserData)
|
|
pStyle = pUserData->m_pStyle;
|
|
float fVerScale = pPiece->m_iVerticalScale / 100.0f;
|
|
|
|
auto pTP = pdfium::MakeUnique<CXFA_TextPiece>();
|
|
pTP->iChars = pPiece->m_iChars;
|
|
pTP->szText = pPiece->GetString();
|
|
pTP->Widths = pPiece->GetWidths();
|
|
pTP->iBidiLevel = pPiece->m_iBidiLevel;
|
|
pTP->iHorScale = pPiece->m_iHorizontalScale;
|
|
pTP->iVerScale = pPiece->m_iVerticalScale;
|
|
m_textParser.GetUnderline(m_pTextProvider, pStyle.Get(), pTP->iUnderline,
|
|
pTP->iPeriod);
|
|
m_textParser.GetLinethrough(m_pTextProvider, pStyle.Get(),
|
|
pTP->iLineThrough);
|
|
pTP->dwColor = m_textParser.GetColor(m_pTextProvider, pStyle.Get());
|
|
pTP->pFont =
|
|
m_textParser.GetFont(m_pDoc.Get(), m_pTextProvider, pStyle.Get());
|
|
pTP->fFontSize = m_textParser.GetFontSize(m_pTextProvider, pStyle.Get());
|
|
pTP->rtPiece.left = pPiece->m_iStartPos / 20000.0f;
|
|
pTP->rtPiece.width = pPiece->m_iWidth / 20000.0f;
|
|
pTP->rtPiece.height =
|
|
static_cast<float>(pPiece->m_iFontSize) * fVerScale / 20.0f;
|
|
float fBaseLineTemp =
|
|
m_textParser.GetBaseline(m_pTextProvider, pStyle.Get());
|
|
pTP->rtPiece.top = fBaseLineTemp;
|
|
|
|
float fLineHeight = m_textParser.GetLineHeight(
|
|
m_pTextProvider, pStyle.Get(), m_iLines == 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->m_pLinkData : nullptr;
|
|
pPieceLine->m_textPieces.push_back(std::move(pTP));
|
|
DoTabstops(pStyle.Get(), pPieceLine);
|
|
}
|
|
for (const auto& pTP : pPieceLine->m_textPieces) {
|
|
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 CFX_BreakPiece* pPiece = m_pBreak->GetBreakPieceUnstable(i);
|
|
CFX_TextUserData* pUserData = pPiece->m_pUserData.Get();
|
|
if (pUserData)
|
|
pStyle = pUserData->m_pStyle;
|
|
float fVerScale = pPiece->m_iVerticalScale / 100.0f;
|
|
float fBaseLine = m_textParser.GetBaseline(m_pTextProvider, pStyle.Get());
|
|
float fLineHeight = m_textParser.GetLineHeight(
|
|
m_pTextProvider, pStyle.Get(), m_iLines == 0, fVerScale);
|
|
if (fBaseLine > 0) {
|
|
float fLineHeightTmp =
|
|
fBaseLine +
|
|
static_cast<float>(pPiece->m_iFontSize) * fVerScale / 20.0f;
|
|
if (fLineHeight < fLineHeightTmp) {
|
|
fLineHeight = fLineHeightTmp;
|
|
}
|
|
}
|
|
fLineStep = std::max(fLineStep, fLineHeight);
|
|
fLineWidth += pPiece->m_iWidth / 20000.0f;
|
|
}
|
|
*pLinePos += fLineStep;
|
|
m_fMaxWidth = std::max(m_fMaxWidth, fLineWidth);
|
|
if (m_pLoader && m_pLoader->bSaveLineHeight) {
|
|
float fHeight = *pLinePos - m_pLoader->fLastPos;
|
|
m_pLoader->fLastPos = *pLinePos;
|
|
m_pLoader->lineHeights.push_back(fHeight);
|
|
}
|
|
}
|
|
|
|
m_pBreak->ClearBreakPieces();
|
|
if (dwStatus == CFX_BreakType::Paragraph) {
|
|
m_pBreak->Reset();
|
|
if (!pStyle && bEndBreak) {
|
|
CXFA_Para* para = m_pTextProvider->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;
|
|
|
|
m_pBreak->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;
|
|
|
|
m_pBreak->SetLineStartPos(fStart);
|
|
}
|
|
m_iLines++;
|
|
}
|
|
|
|
void CXFA_TextLayout::RenderString(CFX_RenderDevice* pDevice,
|
|
CXFA_PieceLine* pPieceLine,
|
|
size_t szPiece,
|
|
std::vector<TextCharPos>* pCharPos,
|
|
const CFX_Matrix& mtDoc2Device) {
|
|
const CXFA_TextPiece* pPiece = pPieceLine->m_textPieces[szPiece].get();
|
|
size_t szCount = GetDisplayPos(pPiece, pCharPos);
|
|
if (szCount > 0) {
|
|
auto span = pdfium::make_span(pCharPos->data(), szCount);
|
|
CFDE_TextOut::DrawString(pDevice, pPiece->dwColor, pPiece->pFont, span,
|
|
pPiece->fFontSize, mtDoc2Device);
|
|
}
|
|
pPieceLine->m_charCounts.push_back(szCount);
|
|
}
|
|
|
|
void CXFA_TextLayout::RenderPath(CFX_RenderDevice* pDevice,
|
|
CXFA_PieceLine* pPieceLine,
|
|
size_t szPiece,
|
|
std::vector<TextCharPos>* pCharPos,
|
|
const CFX_Matrix& mtDoc2Device) {
|
|
CXFA_TextPiece* pPiece = pPieceLine->m_textPieces[szPiece].get();
|
|
bool bNoUnderline = pPiece->iUnderline < 1 || pPiece->iUnderline > 2;
|
|
bool bNoLineThrough = pPiece->iLineThrough < 1 || pPiece->iLineThrough > 2;
|
|
if (bNoUnderline && bNoLineThrough)
|
|
return;
|
|
|
|
CFX_PathData path;
|
|
size_t szChars = GetDisplayPos(pPiece, pCharPos);
|
|
if (szChars > 0) {
|
|
CFX_PointF pt1;
|
|
CFX_PointF pt2;
|
|
float fEndY = (*pCharPos)[0].m_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].m_Origin.x;
|
|
pt2.x = pt1.x +
|
|
(*pCharPos)[j].m_FontCharWidth * pPiece->fFontSize / 1000.0f;
|
|
pt1.y = pt2.y = fEndY;
|
|
path.AppendLine(pt1, pt2);
|
|
}
|
|
fEndY += 2.0f;
|
|
}
|
|
} else {
|
|
pt1.x = (*pCharPos)[0].m_Origin.x;
|
|
pt2.x = (*pCharPos)[szChars - 1].m_Origin.x +
|
|
(*pCharPos)[szChars - 1].m_FontCharWidth * 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].m_Origin.y - pPiece->rtPiece.height * 0.25f;
|
|
pt1.x = (*pCharPos)[0].m_Origin.x;
|
|
pt2.x =
|
|
(*pCharPos)[szChars - 1].m_Origin.x +
|
|
(*pCharPos)[szChars - 1].m_FontCharWidth * 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->m_charCounts[szPiecePrev] > 0) {
|
|
bHasCount = true;
|
|
break;
|
|
}
|
|
}
|
|
if (!bHasCount)
|
|
return;
|
|
|
|
bHasCount = false;
|
|
while (szPieceNext < pPieceLine->m_textPieces.size() - 1) {
|
|
szPieceNext++;
|
|
if (pPieceLine->m_charCounts[szPieceNext] > 0) {
|
|
bHasCount = true;
|
|
break;
|
|
}
|
|
}
|
|
if (!bHasCount)
|
|
return;
|
|
|
|
float fOrgX = 0.0f;
|
|
float fEndX = 0.0f;
|
|
pPiece = pPieceLine->m_textPieces[szPiecePrev].get();
|
|
szChars = GetDisplayPos(pPiece, pCharPos);
|
|
if (szChars < 1)
|
|
return;
|
|
|
|
fOrgX =
|
|
(*pCharPos)[szChars - 1].m_Origin.x +
|
|
(*pCharPos)[szChars - 1].m_FontCharWidth * pPiece->fFontSize / 1000.0f;
|
|
pPiece = pPieceLine->m_textPieces[szPieceNext].get();
|
|
szChars = GetDisplayPos(pPiece, pCharPos);
|
|
if (szChars < 1)
|
|
return;
|
|
|
|
fEndX = (*pCharPos)[0].m_Origin.x;
|
|
CFX_PointF pt1;
|
|
CFX_PointF pt2;
|
|
pt1.x = fOrgX;
|
|
pt2.x = fEndX;
|
|
float fEndY = (*pCharPos)[0].m_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].m_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;
|
|
}
|
|
}
|
|
|
|
CFX_GraphStateData graphState;
|
|
graphState.m_LineCap = CFX_GraphStateData::LineCapButt;
|
|
graphState.m_LineJoin = CFX_GraphStateData::LineJoinMiter;
|
|
graphState.m_LineWidth = 1;
|
|
graphState.m_MiterLimit = 10;
|
|
graphState.m_DashPhase = 0;
|
|
pDevice->DrawPath(&path, &mtDoc2Device, &graphState, 0, pPiece->dwColor, 0);
|
|
}
|
|
|
|
size_t CXFA_TextLayout::GetDisplayPos(const CXFA_TextPiece* pPiece,
|
|
std::vector<TextCharPos>* pCharPos) {
|
|
if (!pPiece || pPiece->iChars < 1)
|
|
return 0;
|
|
return m_pBreak->GetDisplayPos(pPiece, pCharPos);
|
|
}
|