/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/*
* This file is part of the LibreOffice project.
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
#include "rtfdocumentimpl.hxx"
#include <algorithm>
#include <memory>
#include <string_view>
#include <com/sun/star/embed/XEmbeddedObject.hpp>
#include <com/sun/star/beans/PropertyAttribute.hpp>
#include <com/sun/star/io/WrongFormatException.hpp>
#include <com/sun/star/lang/XServiceInfo.hpp>
#include <com/sun/star/lang/XMultiServiceFactory.hpp>
#include <com/sun/star/text/TextContentAnchorType.hpp>
#include <com/sun/star/text/XDependentTextField.hpp>
#include <com/sun/star/document/XDocumentPropertiesSupplier.hpp>
#include <i18nlangtag/languagetag.hxx>
#include <unotools/ucbstreamhelper.hxx>
#include <unotools/streamwrap.hxx>
#include <com/sun/star/drawing/XDrawPageSupplier.hpp>
#include <filter/msfilter/util.hxx>
#include <filter/msfilter/rtfutil.hxx>
#include <comphelper/string.hxx>
#include <comphelper/diagnose_ex.hxx>
#include <tools/globname.hxx>
#include <tools/datetimeutils.hxx>
#include <comphelper/classids.hxx>
#include <comphelper/embeddedobjectcontainer.hxx>
#include <svl/lngmisc.hxx>
#include <sfx2/classificationhelper.hxx>
#include <oox/mathml/imexport.hxx>
#include <ooxml/resourceids.hxx>
#include <oox/token/namespaces.hxx>
#include <oox/drawingml/drawingmltypes.hxx>
#include <rtl/uri.hxx>
#include <rtl/tencinfo.h>
#include <sal/log.hxx>
#include <osl/diagnose.h>
#include <oox/helper/graphichelper.hxx>
#include <vcl/wmfexternal.hxx>
#include <vcl/graph.hxx>
#include <vcl/settings.hxx>
#include <vcl/svapp.hxx>
#include "rtfsdrimport.hxx"
#include "rtfreferenceproperties.hxx"
#include "rtfskipdestination.hxx"
#include "rtftokenizer.hxx"
#include "rtflookahead.hxx"
#include "rtfcharsets.hxx"
#include <unotxdoc.hxx>
#include <unodraw.hxx>
using namespace com::sun::star;
namespace
{
/// Returns an util::DateTime from a 'YYYY. MM. DD.' string.
util::DateTime getDateTimeFromUserProp(std::u16string_view rString)
{
util::DateTime aRet;
size_t nLen = rString.size();
if (nLen >= 4)
{
aRet.Year = o3tl::toInt32(rString.substr(0, 4));
if (nLen >= 8 && o3tl::starts_with(rString.substr(4), u". "))
{
aRet.Month = o3tl::toInt32(rString.substr(6, 2));
if (nLen >= 12 && o3tl::starts_with(rString.substr(8), u". "))
aRet.Day = o3tl::toInt32(rString.substr(10, 2));
}
}
return aRet;
}
} // anonymous namespace
namespace writerfilter::rtftok
{
Id getParagraphBorder(sal_uInt32 nIndex)
{
static const Id aBorderIds[]
= { NS_ooxml::LN_CT_PBdr_top, NS_ooxml::LN_CT_PBdr_left, NS_ooxml::LN_CT_PBdr_bottom,
NS_ooxml::LN_CT_PBdr_right, NS_ooxml::LN_CT_PBdr_between };
return aBorderIds[nIndex];
}
void putNestedAttribute(RTFSprms& rSprms, Id nParent, Id nId, const RTFValue::Pointer_t& pValue,
RTFOverwrite eOverwrite, bool bAttribute)
{
RTFValue::Pointer_t pParent = rSprms.find(nParent, /*bFirst=*/true, /*bForWrite=*/true);
if (!pParent)
{
RTFSprms aAttributes;
if (nParent == NS_ooxml::LN_CT_TcPrBase_shd)
{
// RTF default is 'auto', see writerfilter::dmapper::CellColorHandler
aAttributes.set(NS_ooxml::LN_CT_Shd_color, new RTFValue(sal_uInt32(COL_AUTO)));
aAttributes.set(NS_ooxml::LN_CT_Shd_fill, new RTFValue(sal_uInt32(COL_AUTO)));
}
auto pParentValue = new RTFValue(aAttributes);
rSprms.set(nParent, pParentValue, eOverwrite);
pParent = pParentValue;
}
RTFSprms& rAttributes = (bAttribute ? pParent->getAttributes() : pParent->getSprms());
rAttributes.set(nId, pValue, eOverwrite);
}
void putNestedSprm(RTFSprms& rSprms, Id nParent, Id nId, const RTFValue::Pointer_t& pValue,
RTFOverwrite eOverwrite)
{
putNestedAttribute(rSprms, nParent, nId, pValue, eOverwrite, false);
}
RTFValue::Pointer_t getNestedAttribute(RTFSprms& rSprms, Id nParent, Id nId)
{
RTFValue::Pointer_t pParent = rSprms.find(nParent);
if (!pParent)
return RTFValue::Pointer_t();
RTFSprms& rAttributes = pParent->getAttributes();
return rAttributes.find(nId);
}
RTFValue::Pointer_t getNestedSprm(RTFSprms& rSprms, Id nParent, Id nId)
{
RTFValue::Pointer_t pParent = rSprms.find(nParent);
if (!pParent)
return RTFValue::Pointer_t();
RTFSprms& rInner = pParent->getSprms();
return rInner.find(nId);
}
bool eraseNestedAttribute(RTFSprms& rSprms, Id nParent, Id nId)
{
RTFValue::Pointer_t pParent = rSprms.find(nParent);
if (!pParent)
// It doesn't even have a parent, we're done.
return false;
RTFSprms& rAttributes = pParent->getAttributes();
return rAttributes.erase(nId);
}
RTFSprms& getLastAttributes(RTFSprms& rSprms, Id nId)
{
RTFValue::Pointer_t p = rSprms.find(nId);
if (p && !p->getSprms().empty())
return p->getSprms().back().second->getAttributes();
SAL_WARN("writerfilter.rtf", "trying to set property when no type is defined");
return rSprms;
}
void putBorderProperty(RTFStack& aStates, Id nId, const RTFValue::Pointer_t& pValue)
{
RTFSprms* pAttributes = nullptr;
if (aStates.top().getBorderState() == RTFBorderState::PARAGRAPH_BOX)
for (int i = 0; i < 4; i++)
{
RTFValue::Pointer_t p = aStates.top().getParagraphSprms().find(getParagraphBorder(i));
if (p)
{
RTFSprms& rAttributes = p->getAttributes();
rAttributes.set(nId, pValue);
}
}
else if (aStates.top().getBorderState() == RTFBorderState::CHARACTER)
{
RTFValue::Pointer_t pPointer
= aStates.top().getCharacterSprms().find(NS_ooxml::LN_EG_RPrBase_bdr);
if (pPointer)
{
RTFSprms& rAttributes = pPointer->getAttributes();
rAttributes.set(nId, pValue);
}
}
// Attributes of the last border type
else if (aStates.top().getBorderState() == RTFBorderState::PARAGRAPH)
pAttributes
= &getLastAttributes(aStates.top().getParagraphSprms(), NS_ooxml::LN_CT_PrBase_pBdr);
else if (aStates.top().getBorderState() == RTFBorderState::CELL)
pAttributes = &getLastAttributes(aStates.top().getTableCellSprms(),
NS_ooxml::LN_CT_TcPrBase_tcBorders);
else if (aStates.top().getBorderState() == RTFBorderState::PAGE)
pAttributes = &getLastAttributes(aStates.top().getSectionSprms(),
NS_ooxml::LN_EG_SectPrContents_pgBorders);
else if (aStates.top().getBorderState() == RTFBorderState::NONE)
{
// this is invalid, but Word apparently clears or overrides all paragraph borders now
for (int i = 0; i < 4; ++i)
{
auto const nBorder = getParagraphBorder(i);
RTFSprms aAttributes;
RTFSprms aSprms;
aAttributes.set(NS_ooxml::LN_CT_Border_val,
new RTFValue(NS_ooxml::LN_Value_ST_Border_none));
putNestedSprm(aStates.top().getParagraphSprms(), NS_ooxml::LN_CT_PrBase_pBdr, nBorder,
new RTFValue(aAttributes, aSprms), RTFOverwrite::YES);
}
}
if (pAttributes)
pAttributes->set(nId, pValue);
}
OUString DTTM22OUString(tools::Long nDTTM)
{
return DateTimeToOUString(msfilter::util::DTTM2DateTime(nDTTM));
}
static RTFSprms lcl_getBookmarkProperties(int nPos, const OUString& rString)
{
RTFSprms aAttributes;
auto pPos = new RTFValue(nPos);
if (!rString.isEmpty())
{
// If present, this should be sent first.
auto pString = new RTFValue(rString);
aAttributes.set(NS_ooxml::LN_CT_Bookmark_name, pString);
}
aAttributes.set(NS_ooxml::LN_CT_MarkupRangeBookmark_id, pPos);
return aAttributes;
}
static util::DateTime lcl_getDateTime(RTFParserState const& aState)
{
return { 0 /*100sec*/,
0 /*sec*/,
aState.getMinute(),
aState.getHour(),
aState.getDay(),
aState.getMonth(),
static_cast<sal_Int16>(aState.getYear()),
false };
}
static void lcl_DestinationToMath(OUStringBuffer* pDestinationText,
oox::formulaimport::XmlStreamBuilder& rMathBuffer, bool& rMathNor)
{
if (!pDestinationText)
return;
OUString aStr = pDestinationText->makeStringAndClear();
if (aStr.isEmpty())
return;
rMathBuffer.appendOpeningTag(M_TOKEN(r));
if (rMathNor)
{
rMathBuffer.appendOpeningTag(M_TOKEN(rPr));
// Same as M_TOKEN(lit)
rMathBuffer.appendOpeningTag(M_TOKEN(nor));
rMathBuffer.appendClosingTag(M_TOKEN(nor));
rMathBuffer.appendClosingTag(M_TOKEN(rPr));
rMathNor = false;
}
rMathBuffer.appendOpeningTag(M_TOKEN(t));
rMathBuffer.appendCharacters(aStr);
rMathBuffer.appendClosingTag(M_TOKEN(t));
rMathBuffer.appendClosingTag(M_TOKEN(r));
}
RTFDocumentImpl::RTFDocumentImpl(uno::Reference<uno::XComponentContext> const& xContext,
uno::Reference<io::XInputStream> const& xInputStream,
rtl::Reference<SwXTextDocument> const& xDstDoc,
uno::Reference<frame::XFrame> const& xFrame,
uno::Reference<task::XStatusIndicator> const& xStatusIndicator,
const utl::MediaDescriptor& rMediaDescriptor)
: m_xContext(xContext)
, m_xInputStream(xInputStream)
, m_xDstDoc(xDstDoc)
, m_xFrame(xFrame)
, m_xStatusIndicator(xStatusIndicator)
, m_pMapperStream(nullptr)
, m_aDefaultState(this)
, m_bSkipUnknown(false)
, m_bFirstRun(true)
, m_bFirstRunException(false)
, m_bNeedPap(true)
, m_bNeedCr(false)
, m_bNeedCrOrig(false)
, m_bNeedPar(true)
, m_bNeedFinalPar(false)
, m_nNestedCells(0)
, m_nTopLevelCells(0)
, m_nInheritingCells(0)
, m_nNestedTRLeft(0)
, m_nTopLevelTRLeft(0)
, m_nNestedCurrentCellX(0)
, m_nTopLevelCurrentCellX(0)
, m_nBackupTopLevelCurrentCellX(0)
, m_aTableBufferStack(1) // create top-level buffer already
, m_pSuperstream(nullptr)
, m_nStreamType(0)
, m_nGroupStartPos(0)
, m_nFormFieldType(RTFFormFieldType::NONE)
, m_bObject(false)
, m_nCurrentFontIndex(0)
, m_nCurrentEncoding(-1)
, m_nDefaultFontIndex(-1)
, m_pStyleTableEntries(new RTFReferenceTable::Entries_t)
, m_nCurrentStyleIndex(0)
, m_bFormField(false)
, m_bMathNor(false)
, m_bIgnoreNextContSectBreak(false)
, m_nResetBreakOnSectBreak(RTFKeyword::invalid)
, m_bNeedSect(false) // done by checkFirstRun
, m_bWasInFrame(false)
, m_bHadPicture(false)
, m_bHadSect(false)
, m_nCellxMax(0)
, m_nListPictureId(0)
, m_bIsNewDoc(!rMediaDescriptor.getUnpackedValueOrDefault(u"InsertMode"_ustr, false))
, m_rMediaDescriptor(rMediaDescriptor)
, m_hasRHeader(false)
, m_hasFHeader(false)
, m_hasRFooter(false)
, m_hasFFooter(false)
{
OSL_ASSERT(xInputStream.is());
m_pInStream = utl::UcbStreamHelper::CreateStream(xInputStream, true);
if (m_xDstDoc)
m_xDocumentProperties = m_xDstDoc->getDocumentProperties();
m_pGraphicHelper = std::make_shared<oox::GraphicHelper>(m_xContext, xFrame, oox::StorageRef());
m_pTokenizer = new RTFTokenizer(*this, m_pInStream.get(), m_xStatusIndicator);
m_pSdrImport = new RTFSdrImport(*this, m_xDstDoc);
// unlike OOXML, this is enabled by default
m_aSettingsTableSprms.set(NS_ooxml::LN_CT_Compat_splitPgBreakAndParaMark, new RTFValue(1));
}
RTFDocumentImpl::~RTFDocumentImpl() = default;
SvStream& RTFDocumentImpl::Strm() { return *m_pInStream; }
void RTFDocumentImpl::setSuperstream(RTFDocumentImpl* pSuperstream)
{
m_pSuperstream = pSuperstream;
}
bool RTFDocumentImpl::isSubstream() const { return m_pSuperstream != nullptr; }
void RTFDocumentImpl::finishSubstream() { checkUnicode(/*bUnicode =*/true, /*bHex =*/true); }
void RTFDocumentImpl::resolveSubstream(std::size_t nPos, Id nId)
{
resolveSubstream(nPos, nId, OUString());
}
void RTFDocumentImpl::resolveSubstream(std::size_t nPos, Id nId, OUString const& rIgnoreFirst)
{
sal_uInt64 const nCurrent = Strm().Tell();
// Seek to header position, parse, then seek back.
auto pImpl = new RTFDocumentImpl(m_xContext, m_xInputStream, m_xDstDoc, m_xFrame,
m_xStatusIndicator, m_rMediaDescriptor);
pImpl->setSuperstream(this);
pImpl->m_nStreamType = nId;
pImpl->m_aIgnoreFirst = rIgnoreFirst;
if (!m_aAuthor.isEmpty())
{
pImpl->m_aAuthor = m_aAuthor;
m_aAuthor.clear();
}
if (!m_aAuthorInitials.isEmpty())
{
pImpl->m_aAuthorInitials = m_aAuthorInitials;
m_aAuthorInitials.clear();
}
pImpl->m_nDefaultFontIndex = m_nDefaultFontIndex;
pImpl->m_pStyleTableEntries = m_pStyleTableEntries;
pImpl->Strm().Seek(nPos);
SAL_INFO("writerfilter.rtf", "substream start");
Mapper().substream(nId, pImpl);
SAL_INFO("writerfilter.rtf", "substream end");
Strm().Seek(nCurrent);
}
void RTFDocumentImpl::outputSettingsTable()
{
// tdf#136740: do not change target document settings when pasting
if (!m_bIsNewDoc || isSubstream())
return;
writerfilter::Reference<Properties>::Pointer_t pProp
= new RTFReferenceProperties(m_aSettingsTableAttributes, m_aSettingsTableSprms);
RTFReferenceTable::Entries_t aSettingsTableEntries;
aSettingsTableEntries.insert(std::make_pair(0, pProp));
writerfilter::Reference<Table>::Pointer_t pTable
= new RTFReferenceTable(std::move(aSettingsTableEntries));
Mapper().table(NS_ooxml::LN_settings_settings, pTable);
}
void RTFDocumentImpl::checkFirstRun()
{
if (!m_bFirstRun)
return;
outputSettingsTable();
// start initial paragraph
m_bFirstRun = false;
assert(!m_bNeedSect || m_bFirstRunException);
setNeedSect(true); // first call that succeeds
// set the requested default font, if there are none for each state in stack
RTFValue::Pointer_t pFont
= getNestedAttribute(m_aDefaultState.getCharacterSprms(), NS_ooxml::LN_EG_RPrBase_rFonts,
NS_ooxml::LN_CT_Fonts_ascii);
if (!pFont)
return;
for (size_t i = 0; i < m_aStates.size(); i++)
{
RTFValue::Pointer_t pCurrentFont
= getNestedAttribute(m_aStates[i].getCharacterSprms(), NS_ooxml::LN_EG_RPrBase_rFonts,
NS_ooxml::LN_CT_Fonts_ascii);
if (!pCurrentFont)
putNestedAttribute(m_aStates[i].getCharacterSprms(), NS_ooxml::LN_EG_RPrBase_rFonts,
NS_ooxml::LN_CT_Fonts_ascii, pFont);
}
}
void RTFDocumentImpl::setNeedPar(bool bNeedPar) { m_bNeedPar = bNeedPar; }
void RTFDocumentImpl::setNeedSect(bool bNeedSect)
{
if (!m_bNeedSect && bNeedSect && m_bFirstRun)
{
RTFLookahead aLookahead(Strm(), m_pTokenizer->getGroupStart());
if (aLookahead.hasTable() && aLookahead.hasColumns())
{
m_bFirstRunException = true;
}
}
// ignore setting before checkFirstRun - every keyword calls setNeedSect!
// except the case of a table in a multicolumn section
if (!m_bNeedSect && bNeedSect && (!m_bFirstRun || m_bFirstRunException))
{
if (!m_pSuperstream) // no sections in header/footer!
{
Mapper().startSectionGroup();
}
// set flag in substream too - otherwise multiple startParagraphGroup
m_bNeedSect = bNeedSect;
Mapper().startParagraphGroup();
setNeedPar(true);
}
else if (m_bNeedSect && !bNeedSect)
{
m_bNeedSect = bNeedSect;
}
}
/// Copy rProps to rStyleAttributes and rStyleSprms, but in case of nested sprms, copy their children as toplevel sprms/attributes.
static void lcl_copyFlatten(RTFReferenceProperties& rProps, RTFSprms& rStyleAttributes,
RTFSprms& rStyleSprms)
{
for (auto& rSprm : rProps.getSprms())
{
// createStyleProperties() puts properties to rPr, but here we need a flat list.
if (rSprm.first == NS_ooxml::LN_CT_Style_rPr)
{
// rPr can have both attributes and SPRMs, copy over both types.
RTFSprms& rRPrSprms = rSprm.second->getSprms();
for (const auto& rRPrSprm : rRPrSprms)
rStyleSprms.set(rRPrSprm.first, rRPrSprm.second);
RTFSprms& rRPrAttributes = rSprm.second->getAttributes();
for (const auto& rRPrAttribute : rRPrAttributes)
rStyleAttributes.set(rRPrAttribute.first, rRPrAttribute.second);
}
else
rStyleSprms.set(rSprm.first, rSprm.second);
}
RTFSprms& rAttributes = rProps.getAttributes();
for (const auto& rAttribute : rAttributes)
rStyleAttributes.set(rAttribute.first, rAttribute.second);
}
writerfilter::Reference<Properties>::Pointer_t
RTFDocumentImpl::getProperties(const RTFSprms& rAttributes, RTFSprms const& rSprms, Id nStyleType)
{
RTFSprms aSprms(rSprms);
RTFValue::Pointer_t pAbstractList;
int nAbstractListId = -1;
RTFValue::Pointer_t pNumId
= getNestedSprm(aSprms, NS_ooxml::LN_CT_PPrBase_numPr, NS_ooxml::LN_CT_NumPr_numId);
if (pNumId)
{
// We have a numbering, look up the abstract list for property
// deduplication and duplication.
auto itNumId = m_aListOverrideTable.find(pNumId->getInt());
if (itNumId != m_aListOverrideTable.end())
{
nAbstractListId = itNumId->second;
auto itAbstract = m_aListTable.find(nAbstractListId);
if (itAbstract != m_aListTable.end())
pAbstractList = itAbstract->second;
}
}
if (pAbstractList)
{
auto it = m_aInvalidListTableFirstIndents.find(nAbstractListId);
if (it != m_aInvalidListTableFirstIndents.end())
aSprms.deduplicateList(it->second);
}
int nStyle = 0;
if (!m_aStates.empty())
nStyle = m_aStates.top().getCurrentStyleIndex();
auto it = m_pStyleTableEntries->find(nStyle);
if (it != m_pStyleTableEntries->end())
{
// cloneAndDeduplicate() wants to know about only a single "style", so
// let's merge paragraph and character style properties here.
auto itChar = m_pStyleTableEntries->end();
if (!m_aStates.empty())
{
int nCharStyle = m_aStates.top().getCurrentCharacterStyleIndex();
itChar = m_pStyleTableEntries->find(nCharStyle);
}
RTFSprms aStyleSprms;
RTFSprms aStyleAttributes;
// Ensure the paragraph style is a flat list.
// Take paragraph style into account for character properties as well,
// as paragraph style may contain character properties.
RTFReferenceProperties& rProps = *static_cast<RTFReferenceProperties*>(it->second.get());
lcl_copyFlatten(rProps, aStyleAttributes, aStyleSprms);
if (itChar != m_pStyleTableEntries->end())
{
// Found active character style, then update aStyleSprms/Attributes.
if (!nStyleType || nStyleType == NS_ooxml::LN_Value_ST_StyleType_character)
{
RTFReferenceProperties& rCharProps
= *static_cast<RTFReferenceProperties*>(itChar->second.get());
lcl_copyFlatten(rCharProps, aStyleAttributes, aStyleSprms);
}
}
RTFSprms sprms(aSprms.cloneAndDeduplicate(aStyleSprms, nStyleType, true, &aSprms));
RTFSprms attributes(rAttributes.cloneAndDeduplicate(aStyleAttributes, nStyleType, true));
return new RTFReferenceProperties(std::move(attributes), std::move(sprms));
}
if (pAbstractList)
aSprms.duplicateList(pAbstractList);
writerfilter::Reference<Properties>::Pointer_t pRet
= new RTFReferenceProperties(rAttributes, std::move(aSprms));
return pRet;
}
void RTFDocumentImpl::checkNeedPap()
{
if (!m_bNeedPap)
return;
m_bNeedPap = false; // reset early, so we can avoid recursion when calling ourselves
if (m_aStates.empty())
return;
if (!m_aStates.top().getCurrentBuffer())
{
writerfilter::Reference<Properties>::Pointer_t const pParagraphProperties(getProperties(
m_aStates.top().getParagraphAttributes(), m_aStates.top().getParagraphSprms(),
NS_ooxml::LN_Value_ST_StyleType_paragraph));
// Writer will ignore a page break before a text frame, so guard it with empty paragraphs
const bool bIsInFrame = m_aStates.top().getFrame().hasProperties();
bool hasBreakBeforeFrame
= bIsInFrame
&& m_aStates.top().getParagraphSprms().find(NS_ooxml::LN_CT_PPrBase_pageBreakBefore);
if (hasBreakBeforeFrame)
{
dispatchSymbol(RTFKeyword::PAR);
m_bNeedPap = false;
}
Mapper().props(pParagraphProperties);
if (hasBreakBeforeFrame)
dispatchSymbol(RTFKeyword::PAR);
if (bIsInFrame)
{
writerfilter::Reference<Properties>::Pointer_t const pFrameProperties(
new RTFReferenceProperties(RTFSprms(), m_aStates.top().getFrame().getSprms()));
Mapper().props(pFrameProperties);
}
}
else
{
auto pValue = new RTFValue(m_aStates.top().getParagraphAttributes(),
m_aStates.top().getParagraphSprms());
bufferProperties(*m_aStates.top().getCurrentBuffer(), pValue, nullptr);
}
}
void RTFDocumentImpl::runProps()
{
if (!m_aStates.top().getCurrentBuffer())
{
Reference<Properties>::Pointer_t const pProperties = getProperties(
m_aStates.top().getCharacterAttributes(), m_aStates.top().getCharacterSprms(),
NS_ooxml::LN_Value_ST_StyleType_character);
Mapper().props(pProperties);
}
else
{
auto pValue = new RTFValue(m_aStates.top().getCharacterAttributes(),
m_aStates.top().getCharacterSprms());
bufferProperties(*m_aStates.top().getCurrentBuffer(), pValue, nullptr,
NS_ooxml::LN_Value_ST_StyleType_character);
}
// Delete the sprm, so the trackchange range will be started only once.
// OTOH set a boolean flag, so we'll know we need to end the range later.
RTFValue::Pointer_t pTrackchange
= m_aStates.top().getCharacterSprms().find(NS_ooxml::LN_trackchange);
if (pTrackchange)
{
m_aStates.top().setStartedTrackchange(true);
m_aStates.top().getCharacterSprms().erase(NS_ooxml::LN_trackchange);
}
}
void RTFDocumentImpl::runBreak()
{
sal_Unicode const sBreak[] = { 0x0d };
Mapper().utext(sBreak, 1);
m_bNeedCr = false;
}
void RTFDocumentImpl::tableBreak()
{
checkFirstRun(); // ooo113308-1.rtf has a header at offset 151084 that doesn't startParagraphGroup() without this
runBreak();
Mapper().endParagraphGroup();
Mapper().startParagraphGroup();
}
void RTFDocumentImpl::parBreak()
{
checkFirstRun();
checkNeedPap();
// end previous paragraph
Mapper().startCharacterGroup();
runBreak();
Mapper().endCharacterGroup();
Mapper().endParagraphGroup();
m_bHadPicture = false;
// start new one
if (!m_bParAtEndOfSection)
{
Mapper().startParagraphGroup();
}
}
void RTFDocumentImpl::sectBreak(bool bFinal)
{
SAL_INFO("writerfilter.rtf", __func__ << ": final? " << bFinal << ", needed? " << m_bNeedSect);
bool bNeedSect = m_bNeedSect;
RTFValue::Pointer_t pBreak
= m_aStates.top().getSectionSprms().find(NS_ooxml::LN_EG_SectPrContents_type);
bool bContinuous = pBreak && pBreak->getInt() == NS_ooxml::LN_Value_ST_SectionMark_continuous;
// If there is no paragraph in this section, then insert a dummy one, as required by Writer,
// unless this is the end of the doc, we had nothing since the last section break and this is not a continuous one.
// Also, when pasting, it's fine to not have any paragraph inside the document at all.
if (m_bNeedPar && (!bFinal || m_bNeedSect || bContinuous) && !isSubstream() && m_bIsNewDoc)
{
m_bParAtEndOfSection = true;
dispatchSymbol(RTFKeyword::PAR);
}
// It's allowed to not have a non-table paragraph at the end of an RTF doc, add it now if required.
if (m_bNeedFinalPar && bFinal)
{
dispatchFlag(RTFKeyword::PARD);
m_bParAtEndOfSection = true;
dispatchSymbol(RTFKeyword::PAR);
m_bNeedSect = bNeedSect;
}
// testTdf148515, if RTF ends with \row, endParagraphGroup() must be called!
if (!m_bParAtEndOfSection || m_aStates.top().getCurrentBuffer())
{
Mapper().endParagraphGroup(); // < top para context dies with page break
}
m_bParAtEndOfSection = false;
// paragraph properties are *done* now - only section properties following
while (!m_nHeaderFooterPositions.empty())
{
std::pair<Id, std::size_t> aPair = m_nHeaderFooterPositions.front();
m_nHeaderFooterPositions.pop();
resolveSubstream(aPair.second, aPair.first);
}
// Normally a section break at the end of the doc is necessary. Unless the
// last control word in the document is a section break itself.
if (!bNeedSect || !m_bHadSect)
{
// In case the last section is a continuous one, we don't need to output a section break.
if (bFinal && bContinuous)
m_aStates.top().getSectionSprms().erase(NS_ooxml::LN_EG_SectPrContents_type);
}
// Section properties are a paragraph sprm.
auto pValue
= new RTFValue(m_aStates.top().getSectionAttributes(), m_aStates.top().getSectionSprms());
RTFSprms aAttributes;
RTFSprms aSprms;
aSprms.set(NS_ooxml::LN_CT_PPr_sectPr, pValue);
writerfilter::Reference<Properties>::Pointer_t pProperties
= new RTFReferenceProperties(std::move(aAttributes), std::move(aSprms));
if (bFinal && !m_pSuperstream)
// This is the end of the document, not just the end of e.g. a header.
// This makes sure that dmapper can set DontBalanceTextColumns=true for this section if necessary.
Mapper().markLastSectionGroup();
// The trick is that we send properties of the previous section right now, which will be exactly what dmapper expects.
Mapper().props(pProperties);
// End Section
if (!m_pSuperstream)
{
m_hasFHeader = false;
m_hasRHeader = false;
m_hasRFooter = false;
m_hasFFooter = false;
Mapper().endSectionGroup();
}
m_bNeedPar = false;
m_bNeedSect = false;
}
Color RTFDocumentImpl::getColorTable(sal_uInt32 nIndex)
{
if (!m_pSuperstream)
{
if (nIndex < m_aColorTable.size())
return m_aColorTable[nIndex];
return 0;
}
return m_pSuperstream->getColorTable(nIndex);
}
rtl_TextEncoding RTFDocumentImpl::getEncoding(int nFontIndex)
{
if (!m_pSuperstream)
{
auto it = m_aFontEncodings.find(nFontIndex);
if (it != m_aFontEncodings.end())
// We have a font encoding associated to this font.
return it->second;
if (m_aDefaultState.getCurrentEncoding() != rtl_getTextEncodingFromWindowsCharset(0))
// We have a default encoding.
return m_aDefaultState.getCurrentEncoding();
// Guess based on locale.
return msfilter::util::getBestTextEncodingFromLocale(
Application::GetSettings().GetLanguageTag().getLocale());
}
return m_pSuperstream->getEncoding(nFontIndex);
}
OUString RTFDocumentImpl::getFontName(int nIndex)
{
if (!m_pSuperstream)
return m_aFontNames[nIndex];
return m_pSuperstream->getFontName(nIndex);
}
int RTFDocumentImpl::getFontIndex(int nIndex)
{
if (!m_pSuperstream)
return std::find(m_aFontIndexes.begin(), m_aFontIndexes.end(), nIndex)
- m_aFontIndexes.begin();
return m_pSuperstream->getFontIndex(nIndex);
}
OUString RTFDocumentImpl::getStyleName(int nIndex)
{
if (!m_pSuperstream)
{
OUString aRet;
auto it = m_aStyleNames.find(nIndex);
if (it != m_aStyleNames.end())
aRet = it->second;
return aRet;
}
return m_pSuperstream->getStyleName(nIndex);
}
Id RTFDocumentImpl::getStyleType(int nIndex)
{
if (!m_pSuperstream)
{
Id nRet = 0;
auto it = m_aStyleTypes.find(nIndex);
if (it != m_aStyleTypes.end())
nRet = it->second;
return nRet;
}
return m_pSuperstream->getStyleType(nIndex);
}
RTFParserState& RTFDocumentImpl::getDefaultState()
{
if (!m_pSuperstream)
return m_aDefaultState;
return m_pSuperstream->getDefaultState();
}
oox::GraphicHelper& RTFDocumentImpl::getGraphicHelper() { return *m_pGraphicHelper; }
bool RTFDocumentImpl::isStyleSheetImport()
{
if (m_aStates.empty())
return false;
Destination eDestination = m_aStates.top().getDestination();
return eDestination == Destination::STYLESHEET || eDestination == Destination::STYLEENTRY;
}
void RTFDocumentImpl::resolve(Stream& rMapper)
{
m_pMapperStream = &rMapper;
switch (m_pTokenizer->resolveParse())
{
case RTFError::OK:
SAL_INFO("writerfilter.rtf", "RTFDocumentImpl::resolve: finished without errors");
break;
case RTFError::GROUP_UNDER:
SAL_INFO("writerfilter.rtf", "RTFDocumentImpl::resolve: unmatched '}'");
break;
case RTFError::GROUP_OVER:
SAL_INFO("writerfilter.rtf", "RTFDocumentImpl::resolve: unmatched '{'");
throw io::WrongFormatException(m_pTokenizer->getPosition());
break;
case RTFError::UNEXPECTED_EOF:
SAL_INFO("writerfilter.rtf", "RTFDocumentImpl::resolve: unexpected end of file");
throw io::WrongFormatException(m_pTokenizer->getPosition());
break;
case RTFError::HEX_INVALID:
SAL_INFO("writerfilter.rtf", "RTFDocumentImpl::resolve: invalid hex char");
throw io::WrongFormatException(m_pTokenizer->getPosition());
break;
case RTFError::CHAR_OVER:
SAL_INFO("writerfilter.rtf", "RTFDocumentImpl::resolve: characters after last '}'");
break;
case RTFError::CLASSIFICATION:
SAL_INFO("writerfilter.rtf",
"RTFDocumentImpl::resolve: classification prevented paste");
break;
}
}
void RTFDocumentImpl::resolvePict(bool const bInline, uno::Reference<drawing::XShape> const& rShape)
{
SvMemoryStream aStream;
SvStream* pStream = nullptr;
if (!m_pBinaryData)
{
pStream = &aStream;
int b = 0;
int count = 2;
// Feed the destination text to a stream.
auto& rDestinationTextBuffer = m_aStates.top().getDestinationText();
OString aStr = OUStringToOString(rDestinationTextBuffer, RTL_TEXTENCODING_ASCII_US);
rDestinationTextBuffer.setLength(0);
for (int i = 0; i < aStr.getLength(); ++i)
{
char ch = aStr[i];
if (ch != 0x0d && ch != 0x0a && ch != 0x20)
{
b = b << 4;
sal_Int8 parsed = msfilter::rtfutil::AsHex(ch);
if (parsed == -1)
return;
b += parsed;
count--;
if (!count)
{
aStream.WriteChar(static_cast<char>(b));
count = 2;
b = 0;
}
}
}
}
else
pStream = m_pBinaryData.get();
if (!pStream->Tell())
// No destination text? Then we'll get it later.
return;
SvMemoryStream aDIBStream;
if (m_aStates.top().getPicture().eStyle == RTFBmpStyle::DIBITMAP)
{
// Construct a BITMAPFILEHEADER structure before the real data.
SvStream& rBodyStream = *pStream;
aDIBStream.WriteChar('B');
aDIBStream.WriteChar('M');
// The size of the real data.
aDIBStream.WriteUInt32(rBodyStream.Tell());
// Reserved.
aDIBStream.WriteUInt32(0);
// The offset of the real data, i.e. the size of the header, including this number.
aDIBStream.WriteUInt32(14);
rBodyStream.Seek(0);
aDIBStream.WriteStream(rBodyStream);
pStream = &aDIBStream;
}
// Store, and get its URL.
pStream->Seek(0);
uno::Reference<io::XInputStream> xInputStream(new utl::OInputStreamWrapper(pStream));
WmfExternal aExtHeader;
aExtHeader.mapMode = m_aStates.top().getPicture().eWMetafile;
if (m_aStates.top().getPicture().nGoalWidth == 0
|| m_aStates.top().getPicture().nGoalHeight == 0)
{
// Don't use the values provided by picw and pich if the desired size is provided.
aExtHeader.xExt = sal_uInt16(std::clamp<sal_Int32>(
m_aStates.top().getPicture().nWidth, 0,
SAL_MAX_UINT16)); //TODO: better way to handle out-of-bounds values?
aExtHeader.yExt = sal_uInt16(std::clamp<sal_Int32>(
m_aStates.top().getPicture().nHeight, 0,
SAL_MAX_UINT16)); //TODO: better way to handle out-of-bounds values?
}
WmfExternal* pExtHeader = &aExtHeader;
uno::Reference<lang::XServiceInfo> xServiceInfo(m_aStates.top().getDrawingObject().getShape(),
uno::UNO_QUERY);
if (xServiceInfo.is() && xServiceInfo->supportsService(u"com.sun.star.text.TextFrame"_ustr))
pExtHeader = nullptr;
uno::Reference<graphic::XGraphic> xGraphic
= m_pGraphicHelper->importGraphic(xInputStream, pExtHeader);
if (m_aStates.top().getPicture().eStyle != RTFBmpStyle::NONE)
{
// In case of PNG/JPEG, the real size is known, don't use the values
// provided by picw and pich.
Graphic aGraphic(xGraphic);
Size aSize(aGraphic.GetPrefSize());
MapMode aMap(MapUnit::Map100thMM);
if (aGraphic.GetPrefMapMode().GetMapUnit() == MapUnit::MapPixel)
aSize = Application::GetDefaultDevice()->PixelToLogic(aSize, aMap);
else
aSize = OutputDevice::LogicToLogic(aSize, aGraphic.GetPrefMapMode(), aMap);
m_aStates.top().getPicture().nWidth = aSize.Width();
m_aStates.top().getPicture().nHeight = aSize.Height();
}
uno::Reference<drawing::XShape> xShape(rShape);
if (m_aStates.top().getInShape() && xShape.is())
{
awt::Size aSize = xShape->getSize();
if (aSize.Width || aSize.Height)
{
// resolvePict() is processing pib structure inside shape
// So if shape has dimensions we should use them instead of
// \picwN, \pichN, \picscalexN, \picscaleyN given with picture
m_aStates.top().getPicture().nGoalWidth = aSize.Width;
m_aStates.top().getPicture().nGoalHeight = aSize.Height;
m_aStates.top().getPicture().nScaleX = 100;
m_aStates.top().getPicture().nScaleY = 100;
}
}
// Wrap it in an XShape.
if (xShape.is())
{
uno::Reference<lang::XServiceInfo> xSI(xShape, uno::UNO_QUERY_THROW);
if (!xSI->supportsService(u"com.sun.star.drawing.GraphicObjectShape"_ustr))
{
// it's sometimes an error to get here - but it's possible to have
// a \pict inside the \shptxt of a \shp of shapeType 202 "TextBox"
// and in that case xShape is the text frame; we actually need a
// new GraphicObject then (example: fdo37691-1.rtf)
SAL_INFO("writerfilter.rtf",
"cannot set graphic on existing shape, creating a new GraphicObjectShape");
xShape.clear();
}
}
if (!xShape.is() && m_xDstDoc)
{
xShape.set(m_xDstDoc->createInstance(u"com.sun.star.drawing.GraphicObjectShape"_ustr),
uno::UNO_QUERY);
rtl::Reference<SwFmDrawPage> xShapes = m_xDstDoc->getSwDrawPage();
if (xShapes.is())
xShapes->add(xShape);
}
uno::Reference<beans::XPropertySet> xPropertySet(xShape, uno::UNO_QUERY);
if (xPropertySet.is())
// try..catch is for tdf#161359
try
{
xPropertySet->setPropertyValue(u"Graphic"_ustr, uno::Any(xGraphic));
}
catch (const css::uno::Exception&)
{
DBG_UNHANDLED_EXCEPTION("sw");
}
// check if the picture is in an OLE object and if the \objdata element is used
// (see RTFKeyword::OBJECT in RTFDocumentImpl::dispatchDestination)
if (m_bObject)
{
// Set the object size
awt::Size aSize;
aSize.Width
= (m_aStates.top().getPicture().nGoalWidth ? m_aStates.top().getPicture().nGoalWidth
: m_aStates.top().getPicture().nWidth);
aSize.Height
= (m_aStates.top().getPicture().nGoalHeight ? m_aStates.top().getPicture().nGoalHeight
: m_aStates.top().getPicture().nHeight);
xShape->setSize(aSize);
// Replacement graphic is inline by default, see oox::vml::SimpleShape::implConvertAndInsert().
xPropertySet->setPropertyValue(u"AnchorType"_ustr,
uno::Any(text::TextContentAnchorType_AS_CHARACTER));
auto pShapeValue = new RTFValue(xShape);
m_aObjectAttributes.set(NS_ooxml::LN_shape, pShapeValue);
return;
}
if (m_aStates.top().getInListpicture())
{
// Send the shape directly, no section is started, to additional properties will be ignored anyway.
Mapper().startShape(xShape);
Mapper().endShape();
return;
}
// Send it to the dmapper.
RTFSprms aSprms;
RTFSprms aAttributes;
// shape attribute
RTFSprms aPicAttributes;
if (m_aStates.top().getPicture().nCropT != 0 || m_aStates.top().getPicture().nCropB != 0
|| m_aStates.top().getPicture().nCropL != 0 || m_aStates.top().getPicture().nCropR != 0)
{
text::GraphicCrop const crop{ m_aStates.top().getPicture().nCropT,
m_aStates.top().getPicture().nCropB,
m_aStates.top().getPicture().nCropL,
m_aStates.top().getPicture().nCropR };
auto const pCrop = new RTFValue(crop);
aPicAttributes.set(NS_ooxml::LN_CT_BlipFillProperties_srcRect, pCrop);
}
auto pShapeValue = new RTFValue(xShape);
aPicAttributes.set(NS_ooxml::LN_shape, pShapeValue);
// pic sprm
RTFSprms aGraphicDataAttributes;
RTFSprms aGraphicDataSprms;
auto pPicValue = new RTFValue(aPicAttributes);
aGraphicDataSprms.set(NS_ooxml::LN_pic_pic, pPicValue);
// graphicData sprm
RTFSprms aGraphicAttributes;
RTFSprms aGraphicSprms;
auto pGraphicDataValue = new RTFValue(aGraphicDataAttributes, aGraphicDataSprms);
aGraphicSprms.set(NS_ooxml::LN_CT_GraphicalObject_graphicData, pGraphicDataValue);
// graphic sprm
auto pGraphicValue = new RTFValue(aGraphicAttributes, aGraphicSprms);
// extent sprm
RTFSprms aExtentAttributes;
int nXExt = (m_aStates.top().getPicture().nGoalWidth ? m_aStates.top().getPicture().nGoalWidth
: m_aStates.top().getPicture().nWidth);
int nYExt = (m_aStates.top().getPicture().nGoalHeight ? m_aStates.top().getPicture().nGoalHeight
: m_aStates.top().getPicture().nHeight);
if (m_aStates.top().getPicture().nScaleX != 100)
nXExt = (static_cast<tools::Long>(m_aStates.top().getPicture().nScaleX)
* (nXExt
- (m_aStates.top().getPicture().nCropL + m_aStates.top().getPicture().nCropR)))
/ 100L;
if (m_aStates.top().getPicture().nScaleY != 100)
nYExt = (static_cast<tools::Long>(m_aStates.top().getPicture().nScaleY)
* (nYExt
- (m_aStates.top().getPicture().nCropT + m_aStates.top().getPicture().nCropB)))
/ 100L;
auto pXExtValue = new RTFValue(oox::drawingml::convertHmmToEmu(nXExt));
auto pYExtValue = new RTFValue(oox::drawingml::convertHmmToEmu(nYExt));
aExtentAttributes.set(NS_ooxml::LN_CT_PositiveSize2D_cx, pXExtValue);
aExtentAttributes.set(NS_ooxml::LN_CT_PositiveSize2D_cy, pYExtValue);
auto pExtentValue = new RTFValue(aExtentAttributes);
// docpr sprm
RTFSprms aDocprAttributes;
for (const auto& rCharacterAttribute : m_aStates.top().getCharacterAttributes())
if (rCharacterAttribute.first == NS_ooxml::LN_CT_NonVisualDrawingProps_name
|| rCharacterAttribute.first == NS_ooxml::LN_CT_NonVisualDrawingProps_descr)
aDocprAttributes.set(rCharacterAttribute.first, rCharacterAttribute.second);
auto pDocprValue = new RTFValue(aDocprAttributes);
if (bInline)
{
RTFSprms aInlineAttributes;
aInlineAttributes.set(NS_ooxml::LN_CT_Inline_distT, new RTFValue(0));
aInlineAttributes.set(NS_ooxml::LN_CT_Inline_distB, new RTFValue(0));
aInlineAttributes.set(NS_ooxml::LN_CT_Inline_distL, new RTFValue(0));
aInlineAttributes.set(NS_ooxml::LN_CT_Inline_distR, new RTFValue(0));
RTFSprms aInlineSprms;
aInlineSprms.set(NS_ooxml::LN_CT_Inline_extent, pExtentValue);
aInlineSprms.set(NS_ooxml::LN_CT_Inline_docPr, pDocprValue);
aInlineSprms.set(NS_ooxml::LN_graphic_graphic, pGraphicValue);
// inline sprm
auto pValue = new RTFValue(aInlineAttributes, aInlineSprms);
aSprms.set(NS_ooxml::LN_inline_inline, pValue);
}
else // anchored
{
// wrap sprm
RTFSprms aAnchorWrapAttributes;
m_aStates.top().getShape().getAnchorAttributes().set(
NS_ooxml::LN_CT_Anchor_behindDoc,
new RTFValue((m_aStates.top().getShape().getInBackground()) ? 1 : 0));
RTFSprms aAnchorSprms;
for (const auto& rCharacterAttribute : m_aStates.top().getCharacterAttributes())
{
if (rCharacterAttribute.first == NS_ooxml::LN_CT_WrapSquare_wrapText)
aAnchorWrapAttributes.set(rCharacterAttribute.first, rCharacterAttribute.second);
}
sal_Int32 nWrap = -1;
for (auto& rCharacterSprm : m_aStates.top().getCharacterSprms())
{
if (rCharacterSprm.first == NS_ooxml::LN_EG_WrapType_wrapNone
|| rCharacterSprm.first == NS_ooxml::LN_EG_WrapType_wrapTight)
{
nWrap = rCharacterSprm.first;
// If there is a wrap polygon prepared by RTFSdrImport, pick it up here.
if (rCharacterSprm.first == NS_ooxml::LN_EG_WrapType_wrapTight
&& !m_aStates.top().getShape().getWrapPolygonSprms().empty())
rCharacterSprm.second->getSprms().set(
NS_ooxml::LN_CT_WrapTight_wrapPolygon,
new RTFValue(RTFSprms(), m_aStates.top().getShape().getWrapPolygonSprms()));
aAnchorSprms.set(rCharacterSprm.first, rCharacterSprm.second);
}
}
if (m_aStates.top().getShape().getWrapSprm().first != 0)
// Replay of a buffered shape, wrap sprm there has priority over
// character sprms of the current state.
aAnchorSprms.set(m_aStates.top().getShape().getWrapSprm().first,
m_aStates.top().getShape().getWrapSprm().second);
aAnchorSprms.set(NS_ooxml::LN_CT_Anchor_extent, pExtentValue);
if (!aAnchorWrapAttributes.empty() && nWrap == -1)
aAnchorSprms.set(NS_ooxml::LN_EG_WrapType_wrapSquare,
new RTFValue(aAnchorWrapAttributes));
// See OOXMLFastContextHandler::positionOffset(), we can't just put offset values in an RTFValue.
RTFSprms aPoshAttributes;
RTFSprms aPoshSprms;
if (m_aStates.top().getShape().getHoriOrientRelationToken() > 0)
aPoshAttributes.set(
NS_ooxml::LN_CT_PosH_relativeFrom,
new RTFValue(m_aStates.top().getShape().getHoriOrientRelationToken()));
if (m_aStates.top().getShape().getLeft() != 0)
{
Mapper().positionOffset(OUString::number(oox::drawingml::convertHmmToEmu(
m_aStates.top().getShape().getLeft())),
/*bVertical=*/false);
aPoshSprms.set(NS_ooxml::LN_CT_PosH_posOffset, new RTFValue());
}
aAnchorSprms.set(NS_ooxml::LN_CT_Anchor_positionH,
new RTFValue(aPoshAttributes, aPoshSprms));
RTFSprms aPosvAttributes;
RTFSprms aPosvSprms;
if (m_aStates.top().getShape().getVertOrientRelationToken() > 0)
aPosvAttributes.set(
NS_ooxml::LN_CT_PosV_relativeFrom,
new RTFValue(m_aStates.top().getShape().getVertOrientRelationToken()));
if (m_aStates.top().getShape().getTop() != 0)
{
Mapper().positionOffset(OUString::number(oox::drawingml::convertHmmToEmu(
m_aStates.top().getShape().getTop())),
/*bVertical=*/true);
aPosvSprms.set(NS_ooxml::LN_CT_PosV_posOffset, new RTFValue());
}
aAnchorSprms.set(NS_ooxml::LN_CT_Anchor_positionV,
new RTFValue(aPosvAttributes, aPosvSprms));
aAnchorSprms.set(NS_ooxml::LN_CT_Anchor_docPr, pDocprValue);
aAnchorSprms.set(NS_ooxml::LN_graphic_graphic, pGraphicValue);
// anchor sprm
auto pValue = new RTFValue(m_aStates.top().getShape().getAnchorAttributes(), aAnchorSprms);
aSprms.set(NS_ooxml::LN_anchor_anchor, pValue);
}
checkFirstRun();
if (!m_aStates.top().getCurrentBuffer())
{
writerfilter::Reference<Properties>::Pointer_t pProperties
= new RTFReferenceProperties(std::move(aAttributes), std::move(aSprms));
Mapper().props(pProperties);
// Make sure we don't lose these properties with a too early reset.
m_bHadPicture = true;
}
else
{
auto pValue = new RTFValue(aAttributes, aSprms);
bufferProperties(*m_aStates.top().getCurrentBuffer(), pValue, nullptr);
}
}
RTFError RTFDocumentImpl::resolveChars(char ch)
{
if (m_aStates.top().getInternalState() == RTFInternalState::BIN)
{
m_pBinaryData = std::make_shared<SvMemoryStream>();
m_pBinaryData->WriteChar(ch);
for (int i = 0; i < m_aStates.top().getBinaryToRead() - 1; ++i)
{
Strm().ReadChar(ch);
m_pBinaryData->WriteChar(ch);
}
m_aStates.top().setInternalState(RTFInternalState::NORMAL);
return RTFError::OK;
}
OStringBuffer aBuf(512);
bool bUnicodeChecked = false;
bool bSkipped = false;
while (!Strm().eof()
&& (m_aStates.top().getInternalState() == RTFInternalState::HEX
|| (ch != '{' && ch != '}' && ch != '\\')))
{
if (m_aStates.top().getInternalState() == RTFInternalState::HEX
|| (ch != 0x0d && ch != 0x0a))
{
if (m_aStates.top().getCharsToSkip() == 0)
{
if (!bUnicodeChecked)
{
checkUnicode(/*bUnicode =*/true, /*bHex =*/false);
bUnicodeChecked = true;
}
aBuf.append(ch);
}
else
{
bSkipped = true;
m_aStates.top().getCharsToSkip()--;
}
}
// read a single char if we're in hex mode
if (m_aStates.top().getInternalState() == RTFInternalState::HEX)
break;
if (RTL_TEXTENCODING_MS_932 == m_aStates.top().getCurrentEncoding())
{
unsigned char uch = ch;
if ((uch >= 0x80 && uch <= 0x9F) || uch >= 0xE0)
{
// read second byte of 2-byte Shift-JIS - may be \ { }
Strm().ReadChar(ch);
if (m_aStates.top().getCharsToSkip() == 0)
{
// fdo#79384: Word will reject Shift-JIS following \loch
// but apparently OOo could read and (worse) write such documents
SAL_INFO_IF(m_aStates.top().getRunType() != RTFParserState::RunType::DBCH,
"writerfilter.rtf", "invalid Shift-JIS without DBCH");
assert(bUnicodeChecked);
aBuf.append(ch);
}
else
{
assert(bSkipped);
// anybody who uses \ucN with Shift-JIS is insane
m_aStates.top().getCharsToSkip()--;
}
}
}
Strm().ReadChar(ch);
}
if (m_aStates.top().getInternalState() != RTFInternalState::HEX && !Strm().eof())
Strm().SeekRel(-1);
if (m_aStates.top().getInternalState() == RTFInternalState::HEX
&& m_aStates.top().getDestination() != Destination::LEVELNUMBERS)
{
if (!bSkipped)
{
// note: apparently \'0d\'0a is interpreted as 2 breaks, not 1
if ((ch == '\r' || ch == '\n')
&& m_aStates.top().getDestination() != Destination::DOCCOMM
&& m_aStates.top().getDestination() != Destination::LEVELNUMBERS
&& m_aStates.top().getDestination() != Destination::LEVELTEXT)
{
checkUnicode(/*bUnicode =*/false, /*bHex =*/true);
dispatchSymbol(RTFKeyword::PAR);
}
else
{
m_aHexBuffer.append(ch);
}
}
return RTFError::OK;
}
if (m_aStates.top().getDestination() == Destination::SKIP)
return RTFError::OK;
OString aStr = aBuf.makeStringAndClear();
if (m_aStates.top().getDestination() == Destination::LEVELNUMBERS)
{
if (aStr.toChar() != ';')
m_aStates.top().getLevelNumbers().push_back(sal_Int32(ch));
return RTFError::OK;
}
SAL_INFO("writerfilter.rtf",
"RTFDocumentImpl::resolveChars: collected '"
<< OStringToOUString(aStr, m_aStates.top().getCurrentEncoding()) << "'");
if (m_aStates.top().getDestination() == Destination::COLORTABLE)
{
// we hit a ';' at the end of each color entry
m_aColorTable.push_back(m_aStates.top().getCurrentColor().GetColor());
// set components back to zero
m_aStates.top().getCurrentColor() = RTFColorTableEntry();
}
else if (!aStr.isEmpty())
m_aHexBuffer.append(aStr);
checkUnicode(/*bUnicode =*/false, /*bHex =*/true);
return RTFError::OK;
}
void RTFDocumentImpl::singleChar(sal_uInt8 nValue, bool bRunProps)
{
sal_uInt8 sValue[] = { nValue };
RTFBuffer_t* pCurrentBuffer = m_aStates.top().getCurrentBuffer();
if (!pCurrentBuffer)
{
Mapper().startCharacterGroup();
}
else
{
pCurrentBuffer->emplace_back(BUFFER_STARTRUN, nullptr, nullptr);
}
// Should we send run properties?
if (bRunProps)
runProps();
if (!pCurrentBuffer)
{
Mapper().text(sValue, 1);
Mapper().endCharacterGroup();
}
else
{
auto pValue = new RTFValue(*sValue);
pCurrentBuffer->emplace_back(BUFFER_TEXT, pValue, nullptr);
pCurrentBuffer->emplace_back(BUFFER_ENDRUN, nullptr, nullptr);
}
}
void RTFDocumentImpl::handleFontTableEntry()
{
OUString aName = m_aStates.top().getCurrentDestinationText()->makeStringAndClear();
if (aName.isEmpty())
return;
if (aName.endsWith(";"))
{
aName = aName.copy(0, aName.getLength() - 1);
}
// Old documents can contain no encoding information in fontinfo,
// but there can be font name suffixes: Arial CE is not a special
// font, it is ordinal Arial, but with used cp 1250 encoding.
// Moreover these suffixes have priority over \cpgN and \fcharsetN
// in MS Word.
OUString aFontSuffix;
OUString aNameNoSuffix(aName);
sal_Int32 nLastSpace = aName.lastIndexOf(' ');
if (nLastSpace >= 0)
{
aFontSuffix = aName.copy(nLastSpace + 1);
aNameNoSuffix = aName.copy(0, nLastSpace);
sal_Int32 nEncoding = RTL_TEXTENCODING_DONTKNOW;
for (int i = 0; aRTFFontNameSuffixes[i].codepage != RTL_TEXTENCODING_DONTKNOW; i++)
{
if (aFontSuffix.equalsAscii(aRTFFontNameSuffixes[i].suffix))
{
nEncoding = aRTFFontNameSuffixes[i].codepage;
break;
}
}
if (nEncoding > RTL_TEXTENCODING_DONTKNOW)
{
m_nCurrentEncoding = nEncoding;
m_aStates.top().setCurrentEncoding(m_nCurrentEncoding);
}
else
{
// Unknown suffix: looks like it is just a part of font name, restore it
aNameNoSuffix = aName;
}
}
m_aFontNames[m_nCurrentFontIndex] = aNameNoSuffix;
if (m_nCurrentEncoding >= 0)
{
m_aFontEncodings[m_nCurrentFontIndex] = m_nCurrentEncoding;
m_nCurrentEncoding = -1;
}
m_aStates.top().getTableAttributes().set(NS_ooxml::LN_CT_Font_name,
new RTFValue(aNameNoSuffix));
writerfilter::Reference<Properties>::Pointer_t const pProp(new RTFReferenceProperties(
m_aStates.top().getTableAttributes(), m_aStates.top().getTableSprms()));
//See fdo#47347 initial invalid font entry properties are inserted first,
//so when we attempt to insert the correct ones, there's already an
//entry in the map for them, so the new ones aren't inserted.
auto lb = m_aFontTableEntries.lower_bound(m_nCurrentFontIndex);
if (lb != m_aFontTableEntries.end()
&& !(m_aFontTableEntries.key_comp()(m_nCurrentFontIndex, lb->first)))
lb->second = pProp;
else
m_aFontTableEntries.insert(lb, std::make_pair(m_nCurrentFontIndex, pProp));
}
void RTFDocumentImpl::text(OUString& rString)
{
if (rString.getLength() == 1 && m_aStates.top().getDestination() != Destination::DOCCOMM)
{
// No cheating! Tokenizer ignores bare \r and \n, their hex \'0d / \'0a form doesn't count, either.
sal_Unicode ch = rString[0];
if (ch == 0x0d || ch == 0x0a)
return;
}
bool bRet = true;
switch (m_aStates.top().getDestination())
{
// Note: in stylesheet and revtbl groups are mandatory
case Destination::STYLEENTRY:
case Destination::LISTNAME:
case Destination::REVISIONENTRY:
{
// ; is the end of the entry
bool bEnd = false;
if (rString.endsWith(";"))
{
rString = rString.copy(0, rString.getLength() - 1);
bEnd = true;
}
m_aStates.top().appendDestinationText(rString);
if (bEnd)
{
// always clear, necessary in case of group-less fonttable
OUString const aName
= m_aStates.top().getCurrentDestinationText()->makeStringAndClear();
switch (m_aStates.top().getDestination())
{
case Destination::STYLEENTRY:
{
RTFValue::Pointer_t pType
= m_aStates.top().getTableAttributes().find(NS_ooxml::LN_CT_Style_type);
if (pType)
{
// Word strips whitespace around style names.
m_aStyleNames[m_nCurrentStyleIndex] = aName.trim();
m_aStyleTypes[m_nCurrentStyleIndex] = pType->getInt();
auto pValue = new RTFValue(aName.trim());
m_aStates.top().getTableAttributes().set(NS_ooxml::LN_CT_Style_styleId,
pValue);
m_aStates.top().getTableSprms().set(NS_ooxml::LN_CT_Style_name, pValue);
writerfilter::Reference<Properties>::Pointer_t const pProp(
createStyleProperties());
m_pStyleTableEntries->insert(
std::make_pair(m_nCurrentStyleIndex, pProp));
}
else
SAL_INFO("writerfilter.rtf", "no RTF style type defined, ignoring");
break;
}
case Destination::LISTNAME:
// TODO: what can be done with a list name?
break;
case Destination::REVISIONENTRY:
m_aAuthors[m_aAuthors.size()] = aName;
break;
default:
break;
}
resetAttributes();
resetSprms();
}
}
break;
case Destination::DOCVAR:
{
m_aStates.top().appendDocVar(rString);
}
break;
case Destination::FONTTABLE:
case Destination::FONTENTRY:
case Destination::LEVELTEXT:
case Destination::SHAPEPROPERTYNAME:
case Destination::SHAPEPROPERTYVALUE:
case Destination::BOOKMARKEND:
case Destination::PICT:
case Destination::SHAPEPROPERTYVALUEPICT:
case Destination::FORMFIELDNAME:
case Destination::FORMFIELDLIST:
case Destination::DATAFIELD:
case Destination::AUTHOR:
case Destination::KEYWORDS:
case Destination::OPERATOR:
case Destination::COMPANY:
case Destination::COMMENT:
case Destination::OBJDATA:
case Destination::OBJCLASS:
case Destination::ANNOTATIONDATE:
case Destination::ANNOTATIONAUTHOR:
case Destination::ANNOTATIONREFERENCE:
case Destination::FALT:
case Destination::PARAGRAPHNUMBERING_TEXTAFTER:
case Destination::PARAGRAPHNUMBERING_TEXTBEFORE:
case Destination::TITLE:
case Destination::SUBJECT:
case Destination::DOCCOMM:
case Destination::ATNID:
case Destination::ANNOTATIONREFERENCESTART:
case Destination::ANNOTATIONREFERENCEEND:
case Destination::MR:
case Destination::MCHR:
case Destination::MPOS:
case Destination::MVERTJC:
case Destination::MSTRIKEH:
case Destination::MDEGHIDE:
case Destination::MBEGCHR:
case Destination::MSEPCHR:
case Destination::MENDCHR:
case Destination::MSUBHIDE:
case Destination::MSUPHIDE:
case Destination::MTYPE:
case Destination::MGROW:
case Destination::INDEXENTRY:
case Destination::TOCENTRY:
case Destination::PROPNAME:
case Destination::STATICVAL:
m_aStates.top().appendDestinationText(rString);
break;
case Destination::GENERATOR:
// don't enlarge space sequences, eg. it was saved in LibreOffice
if (!rString.startsWithIgnoreAsciiCase("Microsoft"))
m_aSettingsTableSprms.set(NS_ooxml::LN_CT_Settings_longerSpaceSequence,
new RTFValue(0));
break;
default:
bRet = false;
break;
}
if (bRet)
return;
if (!m_aIgnoreFirst.isEmpty() && m_aIgnoreFirst == rString)
{
m_aIgnoreFirst.clear();
return;
}
// Are we in the middle of the table definition? (No cell defs yet, but we already have some cell props.)
if (m_aStates.top().getTableCellSprms().find(NS_ooxml::LN_CT_TcPrBase_vAlign)
&& m_nTopLevelCells == 0)
{
m_aTableBufferStack.back().emplace_back(BUFFER_UTEXT, new RTFValue(rString), nullptr);
return;
}
checkFirstRun();
checkNeedPap();
// Don't return earlier, a bookmark start has to be in a paragraph group.
if (m_aStates.top().getDestination() == Destination::BOOKMARKSTART)
{
m_aStates.top().appendDestinationText(rString);
return;
}
RTFBuffer_t* pCurrentBuffer = m_aStates.top().getCurrentBuffer();
if (!pCurrentBuffer && m_aStates.top().getDestination() != Destination::FOOTNOTE)
Mapper().startCharacterGroup();
else if (pCurrentBuffer)
{
RTFValue::Pointer_t pValue;
pCurrentBuffer->emplace_back(BUFFER_STARTRUN, pValue, nullptr);
}
if (m_aStates.top().getDestination() == Destination::NORMAL
|| m_aStates.top().getDestination() == Destination::FIELDRESULT
|| m_aStates.top().getDestination() == Destination::SHAPETEXT)
runProps();
if (!pCurrentBuffer)
Mapper().utext(rString.getStr(), rString.getLength());
else
{
auto pValue = new RTFValue(rString);
pCurrentBuffer->emplace_back(BUFFER_UTEXT, pValue, nullptr);
}
m_bNeedCr = true;
if (!pCurrentBuffer && m_aStates.top().getDestination() != Destination::FOOTNOTE)
Mapper().endCharacterGroup();
else if (pCurrentBuffer)
{
RTFValue::Pointer_t pValue;
pCurrentBuffer->emplace_back(BUFFER_ENDRUN, pValue, nullptr);
}
}
void RTFDocumentImpl::prepareProperties(
RTFParserState& rState, writerfilter::Reference<Properties>::Pointer_t& o_rpParagraphProperties,
writerfilter::Reference<Properties>::Pointer_t& o_rpFrameProperties,
writerfilter::Reference<Properties>::Pointer_t& o_rpTableRowProperties, int const nCells,
int const nCurrentCellX)
{
o_rpParagraphProperties
= getProperties(rState.getParagraphAttributes(), rState.getParagraphSprms(),
NS_ooxml::LN_Value_ST_StyleType_paragraph);
if (rState.getFrame().hasProperties())
{
o_rpFrameProperties = new RTFReferenceProperties(RTFSprms(), rState.getFrame().getSprms());
}
// Table width.
RTFValue::Pointer_t const pTableWidthProps
= rState.getTableRowSprms().find(NS_ooxml::LN_CT_TblPrBase_tblW);
if (!pTableWidthProps)
{
auto pUnitValue = new RTFValue(3);
putNestedAttribute(rState.getTableRowSprms(), NS_ooxml::LN_CT_TblPrBase_tblW,
NS_ooxml::LN_CT_TblWidth_type, pUnitValue);
auto pWValue = new RTFValue(nCurrentCellX);
putNestedAttribute(rState.getTableRowSprms(), NS_ooxml::LN_CT_TblPrBase_tblW,
NS_ooxml::LN_CT_TblWidth_w, pWValue);
}
if (nCells > 0)
rState.getTableRowSprms().set(NS_ooxml::LN_tblRow, new RTFValue(1));
RTFValue::Pointer_t const pCellMar
= rState.getTableRowSprms().find(NS_ooxml::LN_CT_TblPrBase_tblCellMar);
if (!pCellMar)
{
// If no cell margins are defined, the default left/right margin is 0 in Word, but not in Writer.
RTFSprms aAttributes;
aAttributes.set(NS_ooxml::LN_CT_TblWidth_type,
new RTFValue(NS_ooxml::LN_Value_ST_TblWidth_dxa));
aAttributes.set(NS_ooxml::LN_CT_TblWidth_w, new RTFValue(0));
putNestedSprm(rState.getTableRowSprms(), NS_ooxml::LN_CT_TblPrBase_tblCellMar,
NS_ooxml::LN_CT_TblCellMar_left, new RTFValue(aAttributes));
putNestedSprm(rState.getTableRowSprms(), NS_ooxml::LN_CT_TblPrBase_tblCellMar,
NS_ooxml::LN_CT_TblCellMar_right, new RTFValue(aAttributes));
}
o_rpTableRowProperties
= new RTFReferenceProperties(rState.getTableRowAttributes(), rState.getTableRowSprms());
}
void RTFDocumentImpl::sendProperties(
writerfilter::Reference<Properties>::Pointer_t const& pParagraphProperties,
writerfilter::Reference<Properties>::Pointer_t const& pFrameProperties,
writerfilter::Reference<Properties>::Pointer_t const& pTableRowProperties)
{
Mapper().props(pParagraphProperties);
if (pFrameProperties)
{
Mapper().props(pFrameProperties);
}
Mapper().props(pTableRowProperties);
tableBreak();
}
void RTFDocumentImpl::replayRowBuffer(RTFBuffer_t& rBuffer, ::std::deque<RTFSprms>& rCellsSprms,
::std::deque<RTFSprms>& rCellsAttributes, int const nCells)
{
for (int i = 0; i < nCells; ++i)
{
replayBuffer(rBuffer, &rCellsSprms.front(), &rCellsAttributes.front());
rCellsSprms.pop_front();
rCellsAttributes.pop_front();
}
for (Buf_t& i : rBuffer)
{
SAL_WARN_IF(BUFFER_CELLEND == std::get<0>(i), "writerfilter.rtf", "dropping table cell!");
}
assert(rCellsSprms.empty());
assert(rCellsAttributes.empty());
}
void RTFDocumentImpl::replayBuffer(RTFBuffer_t& rBuffer, RTFSprms* const pSprms,
RTFSprms const* const pAttributes)
{
while (!rBuffer.empty())
{
Buf_t aTuple(rBuffer.front());
rBuffer.pop_front();
if (std::get<0>(aTuple) == BUFFER_PROPS || std::get<0>(aTuple) == BUFFER_PROPS_CHAR)
{
// Construct properties via getProperties() and not directly, to take care of deduplication.
writerfilter::Reference<Properties>::Pointer_t const pProp(getProperties(
std::get<1>(aTuple)->getAttributes(), std::get<1>(aTuple)->getSprms(),
std::get<0>(aTuple) == BUFFER_PROPS_CHAR ? NS_ooxml::LN_Value_ST_StyleType_character
: 0));
Mapper().props(pProp);
}
else if (std::get<0>(aTuple) == BUFFER_NESTROW)
{
TableRowBuffer& rRowBuffer(*std::get<2>(aTuple));
replayRowBuffer(rRowBuffer.GetBuffer(), rRowBuffer.GetCellsSprms(),
rRowBuffer.GetCellsAttributes(), rRowBuffer.GetCells());
sendProperties(rRowBuffer.GetParaProperties(), rRowBuffer.GetFrameProperties(),
rRowBuffer.GetRowProperties());
}
else if (std::get<0>(aTuple) == BUFFER_CELLEND)
{
assert(pSprms && pAttributes);
auto pValue = new RTFValue(1);
pSprms->set(NS_ooxml::LN_tblCell, pValue);
writerfilter::Reference<Properties>::Pointer_t const pTableCellProperties(
new RTFReferenceProperties(*pAttributes, *pSprms));
Mapper().props(pTableCellProperties);
tableBreak();
break;
}
else if (std::get<0>(aTuple) == BUFFER_STARTRUN)
Mapper().startCharacterGroup();
else if (std::get<0>(aTuple) == BUFFER_TEXT)
{
sal_uInt8 const nValue = std::get<1>(aTuple)->getInt();
Mapper().text(&nValue, 1);
}
else if (std::get<0>(aTuple) == BUFFER_UTEXT)
{
OUString const aString(std::get<1>(aTuple)->getString());
Mapper().utext(aString.getStr(), aString.getLength());
}
else if (std::get<0>(aTuple) == BUFFER_ENDRUN)
Mapper().endCharacterGroup();
else if (std::get<0>(aTuple) == BUFFER_PAR)
parBreak();
else if (std::get<0>(aTuple) == BUFFER_STARTSHAPE)
m_pSdrImport->resolve(std::get<1>(aTuple)->getShape(), false, RTFSdrImport::SHAPE);
else if (std::get<0>(aTuple) == BUFFER_RESOLVESHAPE)
{
// Make sure there is no current buffer while replaying the shape,
// otherwise it gets re-buffered.
RTFBuffer_t* pCurrentBuffer = m_aStates.top().getCurrentBuffer();
m_aStates.top().setCurrentBuffer(nullptr);
// Set current shape during replay, needed by e.g. wrap in
// background.
RTFShape aShape = m_aStates.top().getShape();
m_aStates.top().getShape() = std::get<1>(aTuple)->getShape();
m_pSdrImport->resolve(std::get<1>(aTuple)->getShape(), true, RTFSdrImport::SHAPE);
m_aStates.top().getShape() = std::move(aShape);
m_aStates.top().setCurrentBuffer(pCurrentBuffer);
}
else if (std::get<0>(aTuple) == BUFFER_ENDSHAPE)
m_pSdrImport->close();
else if (std::get<0>(aTuple) == BUFFER_RESOLVESUBSTREAM)
{
RTFSprms& rAttributes = std::get<1>(aTuple)->getAttributes();
std::size_t nPos = rAttributes.find(0)->getInt();
Id nId = rAttributes.find(1)->getInt();
OUString aCustomMark = rAttributes.find(2)->getString();
resolveSubstream(nPos, nId, aCustomMark);
}
else if (std::get<0>(aTuple) == BUFFER_PICTURE)
m_aStates.top().getPicture() = std::get<1>(aTuple)->getPicture();
else if (std::get<0>(aTuple) == BUFFER_SETSTYLE)
{
if (!m_aStates.empty())
m_aStates.top().setCurrentStyleIndex(std::get<1>(aTuple)->getInt());
}
else
assert(false);
}
}
bool findPropertyName(const std::vector<beans::PropertyValue>& rProperties, const OUString& rName)
{
return std::any_of(
rProperties.begin(), rProperties.end(),
[&rName](const beans::PropertyValue& rProperty) { return rProperty.Name == rName; });
}
void RTFDocumentImpl::backupTableRowProperties()
{
if (m_nTopLevelCurrentCellX)
{
m_aBackupTableRowSprms = m_aStates.top().getTableRowSprms();
m_aBackupTableRowAttributes = m_aStates.top().getTableRowAttributes();
m_nBackupTopLevelCurrentCellX = m_nTopLevelCurrentCellX;
}
}
void RTFDocumentImpl::restoreTableRowProperties()
{
m_aStates.top().getTableRowSprms() = m_aBackupTableRowSprms;
m_aStates.top().getTableRowAttributes() = m_aBackupTableRowAttributes;
m_nTopLevelCurrentCellX = m_nBackupTopLevelCurrentCellX;
}
void RTFDocumentImpl::resetTableRowProperties()
{
m_aStates.top().getTableRowSprms() = m_aDefaultState.getTableRowSprms();
m_aStates.top().getTableRowSprms().set(NS_ooxml::LN_CT_TblGridBase_gridCol, new RTFValue(-1),
RTFOverwrite::NO_APPEND);
m_aStates.top().getTableRowAttributes() = m_aDefaultState.getTableRowAttributes();
if (Destination::NESTEDTABLEPROPERTIES == m_aStates.top().getDestination())
{
m_nNestedTRLeft = 0;
m_nNestedCurrentCellX = 0;
}
else
{
m_nTopLevelTRLeft = 0;
m_nTopLevelCurrentCellX = 0;
}
}
RTFError RTFDocumentImpl::dispatchToggle(RTFKeyword nKeyword, bool bParam, int nParam)
{
setNeedSect(true);
checkUnicode(/*bUnicode =*/true, /*bHex =*/true);
RTFSkipDestination aSkip(*this);
int nSprm = -1;
tools::SvRef<RTFValue> pBoolValue(new RTFValue(int(!bParam || nParam != 0)));
// Underline toggles.
switch (nKeyword)
{
case RTFKeyword::UL:
nSprm = NS_ooxml::LN_Value_ST_Underline_single;
break;
case RTFKeyword::ULDASH:
nSprm = NS_ooxml::LN_Value_ST_Underline_dash;
break;
case RTFKeyword::ULDASHD:
nSprm = NS_ooxml::LN_Value_ST_Underline_dotDash;
break;
case RTFKeyword::ULDASHDD:
nSprm = NS_ooxml::LN_Value_ST_Underline_dotDotDash;
break;
case RTFKeyword::ULDB:
nSprm = NS_ooxml::LN_Value_ST_Underline_double;
break;
case RTFKeyword::ULHWAVE:
nSprm = NS_ooxml::LN_Value_ST_Underline_wavyHeavy;
break;
case RTFKeyword::ULLDASH:
nSprm = NS_ooxml::LN_Value_ST_Underline_dashLong;
break;
case RTFKeyword::ULTH:
nSprm = NS_ooxml::LN_Value_ST_Underline_thick;
break;
case RTFKeyword::ULTHD:
nSprm = NS_ooxml::LN_Value_ST_Underline_dottedHeavy;
break;
case RTFKeyword::ULTHDASH:
nSprm = NS_ooxml::LN_Value_ST_Underline_dashedHeavy;
break;
case RTFKeyword::ULTHDASHD:
nSprm = NS_ooxml::LN_Value_ST_Underline_dashDotHeavy;
break;
case RTFKeyword::ULTHDASHDD:
nSprm = NS_ooxml::LN_Value_ST_Underline_dashDotDotHeavy;
break;
case RTFKeyword::ULTHLDASH:
nSprm = NS_ooxml::LN_Value_ST_Underline_dashLongHeavy;
break;
case RTFKeyword::ULULDBWAVE:
nSprm = NS_ooxml::LN_Value_ST_Underline_wavyDouble;
break;
case RTFKeyword::ULWAVE:
nSprm = NS_ooxml::LN_Value_ST_Underline_wave;
break;
default:
break;
}
if (nSprm >= 0)
{
auto pValue
= new RTFValue((!bParam || nParam != 0) ? nSprm : NS_ooxml::LN_Value_ST_Underline_none);
m_aStates.top().getCharacterAttributes().set(NS_ooxml::LN_CT_Underline_val, pValue);
return RTFError::OK;
}
// Accent characters (over dot / over comma).
switch (nKeyword)
{
case RTFKeyword::ACCNONE:
nSprm = NS_ooxml::LN_Value_ST_Em_none;
break;
case RTFKeyword::ACCDOT:
nSprm = NS_ooxml::LN_Value_ST_Em_dot;
break;
case RTFKeyword::ACCCOMMA:
nSprm = NS_ooxml::LN_Value_ST_Em_comma;
break;
case RTFKeyword::ACCCIRCLE:
nSprm = NS_ooxml::LN_Value_ST_Em_circle;
break;
case RTFKeyword::ACCUNDERDOT:
nSprm = NS_ooxml::LN_Value_ST_Em_underDot;
break;
default:
break;
}
if (nSprm >= 0)
{
auto pValue = new RTFValue((!bParam || nParam != 0) ? nSprm : 0);
m_aStates.top().getCharacterSprms().set(NS_ooxml::LN_EG_RPrBase_em, pValue);
return RTFError::OK;
}
// Trivial character sprms.
switch (nKeyword)
{
case RTFKeyword::B:
case RTFKeyword::AB:
switch (m_aStates.top().getRunType())
{
case RTFParserState::RunType::HICH:
case RTFParserState::RunType::RTLCH_LTRCH_1:
case RTFParserState::RunType::LTRCH_RTLCH_2:
nSprm = NS_ooxml::LN_EG_RPrBase_bCs;
break;
case RTFParserState::RunType::NONE:
case RTFParserState::RunType::LOCH:
case RTFParserState::RunType::LTRCH_RTLCH_1:
case RTFParserState::RunType::RTLCH_LTRCH_2:
case RTFParserState::RunType::DBCH:
default:
nSprm = NS_ooxml::LN_EG_RPrBase_b;
break;
}
break;
case RTFKeyword::I:
case RTFKeyword::AI:
switch (m_aStates.top().getRunType())
{
case RTFParserState::RunType::HICH:
case RTFParserState::RunType::RTLCH_LTRCH_1:
case RTFParserState::RunType::LTRCH_RTLCH_2:
nSprm = NS_ooxml::LN_EG_RPrBase_iCs;
break;
case RTFParserState::RunType::NONE:
case RTFParserState::RunType::LOCH:
case RTFParserState::RunType::LTRCH_RTLCH_1:
case RTFParserState::RunType::RTLCH_LTRCH_2:
case RTFParserState::RunType::DBCH:
default:
nSprm = NS_ooxml::LN_EG_RPrBase_i;
break;
}
break;
case RTFKeyword::OUTL:
nSprm = NS_ooxml::LN_EG_RPrBase_outline;
break;
case RTFKeyword::SHAD:
nSprm = NS_ooxml::LN_EG_RPrBase_shadow;
break;
case RTFKeyword::V:
nSprm = NS_ooxml::LN_EG_RPrBase_vanish;
break;
case RTFKeyword::STRIKE:
nSprm = NS_ooxml::LN_EG_RPrBase_strike;
break;
case RTFKeyword::STRIKED:
nSprm = NS_ooxml::LN_EG_RPrBase_dstrike;
break;
case RTFKeyword::SCAPS:
nSprm = NS_ooxml::LN_EG_RPrBase_smallCaps;
break;
case RTFKeyword::IMPR:
nSprm = NS_ooxml::LN_EG_RPrBase_imprint;
break;
case RTFKeyword::CAPS:
nSprm = NS_ooxml::LN_EG_RPrBase_caps;
break;
default:
break;
}
if (nSprm >= 0)
{
if (m_aStates.top().getDestination() == Destination::LISTLEVEL)
{
m_aStates.top().getTableSprms().set(nSprm, pBoolValue);
}
else
{
m_aStates.top().getCharacterSprms().set(nSprm, pBoolValue);
}
return RTFError::OK;
}
switch (nKeyword)
{
case RTFKeyword::ASPALPHA:
m_aStates.top().getParagraphSprms().set(NS_ooxml::LN_CT_PPrBase_autoSpaceDE,
pBoolValue);
break;
case RTFKeyword::DELETED:
case RTFKeyword::REVISED:
{
auto pValue
= new RTFValue(nKeyword == RTFKeyword::DELETED ? oox::XML_del : oox::XML_ins);
putNestedAttribute(m_aStates.top().getCharacterSprms(), NS_ooxml::LN_trackchange,
NS_ooxml::LN_token, pValue);
}
break;
case RTFKeyword::SBAUTO:
putNestedAttribute(m_aStates.top().getParagraphSprms(), NS_ooxml::LN_CT_PPrBase_spacing,
NS_ooxml::LN_CT_Spacing_beforeAutospacing, pBoolValue);
break;
case RTFKeyword::SAAUTO:
putNestedAttribute(m_aStates.top().getParagraphSprms(), NS_ooxml::LN_CT_PPrBase_spacing,
NS_ooxml::LN_CT_Spacing_afterAutospacing, pBoolValue);
break;
case RTFKeyword::FACINGP:
m_aSettingsTableSprms.set(NS_ooxml::LN_CT_Settings_evenAndOddHeaders, pBoolValue);
break;
case RTFKeyword::HYPHAUTO:
m_aSettingsTableSprms.set(NS_ooxml::LN_CT_Settings_autoHyphenation, pBoolValue);
break;
case RTFKeyword::HYPHCAPS:
pBoolValue = new RTFValue(int(nParam == 0));
m_aSettingsTableSprms.set(NS_ooxml::LN_CT_Settings_doNotHyphenateCaps, pBoolValue);
break;
case RTFKeyword::HYPHPAR:
m_aStates.top().getParagraphSprms().set(NS_ooxml::LN_CT_PPrBase_suppressAutoHyphens,
new RTFValue(int(bParam && nParam == 0)));
break;
default:
{
SAL_INFO("writerfilter.rtf",
"TODO handle toggle '" << RTFTokenizer::toString(nKeyword) << "'");
aSkip.setParsed(false);
}
break;
}
return RTFError::OK;
}
RTFError RTFDocumentImpl::pushState()
{
//SAL_INFO("writerfilter.rtf", __func__ << " before push: " << m_pTokenizer->getGroup());
checkUnicode(/*bUnicode =*/true, /*bHex =*/true);
m_nGroupStartPos = Strm().Tell();
if (m_aStates.empty())
m_aStates.push(m_aDefaultState);
else
{
// fdo#85812 group resets run type of _current_ and new state (but not RTL)
if (m_aStates.top().getRunType() != RTFParserState::RunType::LTRCH_RTLCH_2
&& m_aStates.top().getRunType() != RTFParserState::RunType::RTLCH_LTRCH_2)
{
m_aStates.top().setRunType(RTFParserState::RunType::NONE);
}
if (m_aStates.top().getDestination() == Destination::MR)
lcl_DestinationToMath(m_aStates.top().getCurrentDestinationText(), m_aMathBuffer,
m_bMathNor);
m_aStates.push(m_aStates.top());
}
m_aStates.top().getDestinationText().setLength(0); // was copied: always reset!
m_pTokenizer->pushGroup();
switch (m_aStates.top().getDestination())
{
case Destination::FONTTABLE:
// this is a "faked" destination for the font entry
m_aStates.top().setCurrentDestinationText(&m_aStates.top().getDestinationText());
m_aStates.top().setDestination(Destination::FONTENTRY);
break;
case Destination::STYLESHEET:
// this is a "faked" destination for the style sheet entry
m_aStates.top().setCurrentDestinationText(&m_aStates.top().getDestinationText());
m_aStates.top().setDestination(Destination::STYLEENTRY);
{
// the *default* is \s0 i.e. paragraph style default
// this will be overwritten by \sN \csN \dsN \tsN
m_nCurrentStyleIndex = 0;
auto pValue = new RTFValue(NS_ooxml::LN_Value_ST_StyleType_paragraph);
m_aStates.top().getTableAttributes().set(NS_ooxml::LN_CT_Style_type, pValue);
}
break;
case Destination::FIELDRESULT:
case Destination::SHAPETEXT:
case Destination::FORMFIELD:
//TODO: if this is pushed then the font encoding is used which results in a broken command string
// if it is not pushed to NORMAL then it is not restored in time.
case Destination::FIELDINSTRUCTION:
case Destination::PICT:
m_aStates.top().setDestination(Destination::NORMAL);
break;
case Destination::MNUM:
case Destination::MDEN:
case Destination::ME:
case Destination::MFNAME:
case Destination::MLIM:
case Destination::MSUB:
case Destination::MSUP:
case Destination::MDEG:
case Destination::MOMATH:
m_aStates.top().setDestination(Destination::MR);
break;
case Destination::REVISIONTABLE:
// this is a "faked" destination for the revision table entry
m_aStates.top().setCurrentDestinationText(&m_aStates.top().getDestinationText());
m_aStates.top().setDestination(Destination::REVISIONENTRY);
break;
default:
break;
}
// If this is true, then ooxml:endtrackchange will be generated. Make sure
// we don't generate more ooxml:endtrackchange than ooxml:trackchange: new
// state does not inherit this flag.
m_aStates.top().setStartedTrackchange(false);
return RTFError::OK;
}
writerfilter::Reference<Properties>::Pointer_t RTFDocumentImpl::createStyleProperties()
{
int nBasedOn = 0;
RTFValue::Pointer_t pBasedOn
= m_aStates.top().getTableSprms().find(NS_ooxml::LN_CT_Style_basedOn);
if (pBasedOn)
nBasedOn = pBasedOn->getInt();
if (nBasedOn == 0)
{
// No parent style, then mimic what Word does: ignore attributes which
// would set a margin as formatting, but with a default value.
for (const auto& nId :
{ NS_ooxml::LN_CT_Ind_firstLine, NS_ooxml::LN_CT_Ind_left, NS_ooxml::LN_CT_Ind_right,
NS_ooxml::LN_CT_Ind_start, NS_ooxml::LN_CT_Ind_end })
{
RTFValue::Pointer_t pValue = getNestedAttribute(m_aStates.top().getParagraphSprms(),
NS_ooxml::LN_CT_PPrBase_ind, nId);
if (pValue && pValue->getInt() == 0)
eraseNestedAttribute(m_aStates.top().getParagraphSprms(),
NS_ooxml::LN_CT_PPrBase_ind, nId);
}
}
RTFValue::Pointer_t pParaProps = new RTFValue(m_aStates.top().getParagraphAttributes(),
m_aStates.top().getParagraphSprms());
RTFValue::Pointer_t pCharProps = new RTFValue(m_aStates.top().getCharacterAttributes(),
m_aStates.top().getCharacterSprms());
// resetSprms will clean up this modification
m_aStates.top().getTableSprms().set(NS_ooxml::LN_CT_Style_pPr, pParaProps);
m_aStates.top().getTableSprms().set(NS_ooxml::LN_CT_Style_rPr, pCharProps);
writerfilter::Reference<Properties>::Pointer_t pProps(new RTFReferenceProperties(
m_aStates.top().getTableAttributes(), m_aStates.top().getTableSprms()));
return pProps;
}
/** 2 different representations of the styles are needed:
1) flat content, as read from the input file:
stored in m_pStyleTableEntries, used as reference input for
deduplication both here and for hard formatting in getProperties()
2) real content, with proper override of sprms/attributes where it differs
from parent style; this is produced here and sent to domain mapper
*/
RTFReferenceTable::Entries_t RTFDocumentImpl::deduplicateStyleTable()
{
RTFReferenceTable::Entries_t ret;
for (auto const& it : *m_pStyleTableEntries)
{
auto pStyle = it.second;
ret[it.first] = pStyle;
// ugly downcasts here, but can't easily replace the members with
// RTFReferenceProperties because dmapper wants SvRef<Properties> anyway
RTFValue::Pointer_t const pBasedOn(
static_cast<RTFReferenceProperties&>(*pStyle).getSprms().find(
NS_ooxml::LN_CT_Style_basedOn));
if (pBasedOn)
{
int const nBasedOn(pBasedOn->getInt());
// don't deduplicate yourself - especially a potential problem for the default style.
if (it.first == nBasedOn)
continue;
auto const itParent(m_pStyleTableEntries->find(nBasedOn)); // definition as read!
if (itParent != m_pStyleTableEntries->end())
{
auto const pStyleType(
static_cast<RTFReferenceProperties&>(*pStyle).getAttributes().find(
NS_ooxml::LN_CT_Style_type));
assert(pStyleType);
int const nStyleType(pStyleType->getInt());
RTFSprms sprms(
static_cast<RTFReferenceProperties&>(*pStyle).getSprms().cloneAndDeduplicate(
static_cast<RTFReferenceProperties&>(*itParent->second).getSprms(),
nStyleType));
RTFSprms attributes(
static_cast<RTFReferenceProperties&>(*pStyle)
.getAttributes()
.cloneAndDeduplicate(
static_cast<RTFReferenceProperties&>(*itParent->second).getAttributes(),
nStyleType));
ret[it.first] = new RTFReferenceProperties(std::move(attributes), std::move(sprms));
}
else
{
SAL_WARN("writerfilter.rtf", "parent style not found: " << nBasedOn);
}
}
}
assert(ret.size() == m_pStyleTableEntries->size());
return ret;
}
void RTFDocumentImpl::resetSprms()
{
m_aStates.top().getTableSprms().clear();
m_aStates.top().getCharacterSprms().clear();
m_aStates.top().getParagraphSprms().clear();
}
void RTFDocumentImpl::resetAttributes()
{
m_aStates.top().getTableAttributes().clear();
m_aStates.top().getCharacterAttributes().clear();
m_aStates.top().getParagraphAttributes().clear();
}
static bool lcl_containsProperty(const uno::Sequence<beans::Property>& rProperties,
std::u16string_view rName)
{
return std::any_of(rProperties.begin(), rProperties.end(),
[&](const beans::Property& rProperty) { return rProperty.Name == rName; });
}
RTFError RTFDocumentImpl::beforePopState(RTFParserState& rState)
{
switch (rState.getDestination())
{
//Note: in fonttbl there may or may not be groups, so process it as no groups
case Destination::FONTTABLE:
case Destination::FONTENTRY:
{
// Some text unhandled? Seems it is last font name
if (m_aStates.top().getCurrentDestinationText()->getLength())
handleFontTableEntry();
if (rState.getDestination() == Destination::FONTTABLE)
{
writerfilter::Reference<Table>::Pointer_t const pTable(
new RTFReferenceTable(m_aFontTableEntries));
Mapper().table(NS_ooxml::LN_FONTTABLE, pTable);
if (m_nDefaultFontIndex >= 0)
{
auto pValue = new RTFValue(m_aFontNames[getFontIndex(m_nDefaultFontIndex)]);
putNestedAttribute(m_aDefaultState.getCharacterSprms(),
NS_ooxml::LN_EG_RPrBase_rFonts, NS_ooxml::LN_CT_Fonts_ascii,
pValue);
}
}
}
break;
case Destination::STYLESHEET:
{
RTFReferenceTable::Entries_t pStyleTableDeduplicated(deduplicateStyleTable());
writerfilter::Reference<Table>::Pointer_t const pTable(
new RTFReferenceTable(std::move(pStyleTableDeduplicated)));
Mapper().table(NS_ooxml::LN_STYLESHEET, pTable);
}
break;
case Destination::LISTOVERRIDETABLE:
{
RTFSprms aListTableAttributes;
writerfilter::Reference<Properties>::Pointer_t pProp
= new RTFReferenceProperties(std::move(aListTableAttributes), m_aListTableSprms);
RTFReferenceTable::Entries_t aListTableEntries;
aListTableEntries.insert(std::make_pair(0, pProp));
writerfilter::Reference<Table>::Pointer_t const pTable(
new RTFReferenceTable(std::move(aListTableEntries)));
Mapper().table(NS_ooxml::LN_NUMBERING, pTable);
}
break;
case Destination::LISTENTRY:
for (const auto& rListLevelEntry : rState.getListLevelEntries())
rState.getTableSprms().set(rListLevelEntry.first, rListLevelEntry.second,
RTFOverwrite::NO_APPEND);
break;
case Destination::FIELDINSTRUCTION:
{
auto pValue = new RTFValue(m_aFormfieldAttributes, m_aFormfieldSprms);
RTFSprms aFFAttributes;
RTFSprms aFFSprms;
aFFSprms.set(NS_ooxml::LN_ffdata, pValue);
if (!m_aStates.top().getCurrentBuffer())
{
writerfilter::Reference<Properties>::Pointer_t pProperties
= new RTFReferenceProperties(std::move(aFFAttributes), std::move(aFFSprms));
Mapper().props(pProperties);
}
else
{
auto pFFValue = new RTFValue(aFFAttributes, aFFSprms);
bufferProperties(*m_aStates.top().getCurrentBuffer(), pFFValue, nullptr);
}
m_aFormfieldAttributes.clear();
m_aFormfieldSprms.clear();
if (m_aStates.top().isFieldLocked())
singleChar(cFieldLock);
singleChar(cFieldSep, true);
}
break;
case Destination::FIELDRESULT:
singleChar(cFieldEnd);
if (!m_aPicturePath.isEmpty())
{
// Read the picture into m_aStates.top().aDestinationText.
pushState();
dispatchDestination(RTFKeyword::PICT);
if (m_aPicturePath.endsWith(".png"))
dispatchFlag(RTFKeyword::PNGBLIP);
OUString aFileURL = m_rMediaDescriptor.getUnpackedValueOrDefault(
utl::MediaDescriptor::PROP_URL, OUString());
OUString aPictureURL;
try
{
aPictureURL = rtl::Uri::convertRelToAbs(aFileURL, m_aPicturePath);
}
catch (const rtl::MalformedUriException& rException)
{
SAL_WARN("writerfilter.rtf",
"rtl::Uri::convertRelToAbs() failed: " << rException.getMessage());
}
if (!aPictureURL.isEmpty())
{
SvFileStream aStream(aPictureURL, StreamMode::READ);
if (aStream.IsOpen())
{
OUStringBuffer aBuf;
while (aStream.good())
{
unsigned char ch = 0;
aStream.ReadUChar(ch);
if (ch < 16)
aBuf.append("0");
aBuf.append(static_cast<sal_Int32>(ch), 16);
}
m_aStates.top().getDestinationText() = std::move(aBuf);
}
}
popState();
m_aPicturePath.clear();
}
break;
case Destination::LEVELTEXT:
{
if (&m_aStates.top().getDestinationText()
!= m_aStates.top().getCurrentDestinationText())
break; // not for nested group
OUString aStr = m_aStates.top().getCurrentDestinationText()->makeStringAndClear();
// The first character is the length of the string (the rest should be ignored).
sal_Int32 nLength(aStr.toChar());
OUString aValue;
if (nLength < aStr.getLength())
aValue = aStr.copy(1, nLength);
else
aValue = aStr;
auto pValue = new RTFValue(aValue, true);
rState.getTableAttributes().set(NS_ooxml::LN_CT_LevelText_val, pValue);
}
break;
case Destination::LEVELNUMBERS:
{
bool bNestedLevelNumbers = false;
if (m_aStates.size() > 1)
// Current destination is levelnumbers and parent destination is levelnumbers as well.
bNestedLevelNumbers
= m_aStates[m_aStates.size() - 2].getDestination() == Destination::LEVELNUMBERS;
if (!bNestedLevelNumbers && rState.getTableSprms().find(NS_ooxml::LN_CT_Lvl_lvlText))
{
RTFSprms& rAttributes
= rState.getTableSprms().find(NS_ooxml::LN_CT_Lvl_lvlText)->getAttributes();
RTFValue::Pointer_t pValue = rAttributes.find(NS_ooxml::LN_CT_LevelText_val);
if (pValue && rState.getLevelNumbersValid())
{
OUString aOrig = pValue->getString();
OUStringBuffer aBuf(aOrig.getLength() * 2);
sal_Int32 nReplaces = 1;
for (int i = 0; i < aOrig.getLength(); i++)
{
if (std::find(rState.getLevelNumbers().begin(),
rState.getLevelNumbers().end(), i + 1)
!= rState.getLevelNumbers().end())
{
aBuf.append('%');
// '1.1.1' -> '%1.%2.%3', but '1.' (with '2.' prefix omitted) is %2.
aBuf.append(sal_Int32(nReplaces++ + rState.getListLevelNum() + 1
- rState.getLevelNumbers().size()));
}
else
aBuf.append(aOrig[i]);
}
pValue->setString(aBuf.makeStringAndClear());
}
else if (pValue)
// Have a value, but levelnumbers is not valid -> ignore it.
pValue->setString(OUString());
}
break;
}
case Destination::SHAPEPROPERTYNAME:
if (&m_aStates.top().getDestinationText()
!= m_aStates.top().getCurrentDestinationText())
break; // not for nested group
rState.getShape().getProperties().emplace_back(
m_aStates.top().getCurrentDestinationText()->makeStringAndClear(), OUString());
break;
case Destination::SHAPEPROPERTYVALUE:
if (!rState.getShape().getProperties().empty())
{
rState.getShape().getProperties().back().second
= m_aStates.top().getCurrentDestinationText()->makeStringAndClear();
if (m_aStates.top().getHadShapeText())
m_pSdrImport->append(rState.getShape().getProperties().back().first,
rState.getShape().getProperties().back().second);
else if (rState.getInShapeGroup() && !rState.getInShape()
&& rState.getShape().getProperties().back().first == "rotation")
{
// Rotation should be applied on the groupshape itself, not on each shape.
rState.getShape().getGroupProperties().push_back(
rState.getShape().getProperties().back());
rState.getShape().getProperties().pop_back();
}
}
break;
case Destination::PICPROP:
case Destination::SHAPEINSTRUCTION:
if (m_aStates.size() > 1
&& m_aStates[m_aStates.size() - 2].getDestination()
== Destination::SHAPEINSTRUCTION)
{
// Do not resolve shape if shape instruction destination is inside other shape instruction
}
else if (!m_bObject && !rState.getInListpicture() && !rState.getHadShapeText()
&& (!rState.getInShapeGroup() || rState.getInShape()))
{
// Don't trigger a shape import in case we're only leaving the \shpinst of the groupshape itself.
RTFSdrImport::ShapeOrPict eType
= (rState.getDestination() == Destination::SHAPEINSTRUCTION)
? RTFSdrImport::SHAPE
: RTFSdrImport::PICT;
if (!m_aStates.top().getCurrentBuffer() || eType != RTFSdrImport::SHAPE)
m_pSdrImport->resolve(m_aStates.top().getShape(), true, eType);
else
{
// Shape inside table: buffer the import to have correct anchor position.
// Also buffer the RTFPicture of the state stack as it contains
// the shape size.
auto pPictureValue = new RTFValue(m_aStates.top().getPicture());
m_aStates.top().getCurrentBuffer()->emplace_back(BUFFER_PICTURE, pPictureValue,
nullptr);
auto pValue = new RTFValue(m_aStates.top().getShape());
// Buffer wrap type.
for (const auto& rCharacterSprm : m_aStates.top().getCharacterSprms())
{
if (rCharacterSprm.first == NS_ooxml::LN_EG_WrapType_wrapNone
|| rCharacterSprm.first == NS_ooxml::LN_EG_WrapType_wrapTight)
{
m_aStates.top().getShape().getWrapSprm() = rCharacterSprm;
break;
}
}
m_aStates.top().getCurrentBuffer()->emplace_back(BUFFER_RESOLVESHAPE, pValue,
nullptr);
}
}
else if (rState.getInShapeGroup() && !rState.getInShape())
{
// End of a groupshape, as we're in shapegroup, but not in a real shape.
for (const auto& rGroupProperty : rState.getShape().getGroupProperties())
m_pSdrImport->appendGroupProperty(rGroupProperty.first, rGroupProperty.second);
rState.getShape().getGroupProperties().clear();
}
break;
case Destination::BOOKMARKSTART:
{
if (&m_aStates.top().getDestinationText()
!= m_aStates.top().getCurrentDestinationText())
break; // not for nested group
OUString aStr = m_aStates.top().getCurrentDestinationText()->makeStringAndClear();
int nPos = m_aBookmarks.size();
m_aBookmarks[aStr] = nPos;
if (!m_aStates.top().getCurrentBuffer())
Mapper().props(new RTFReferenceProperties(lcl_getBookmarkProperties(nPos, aStr)));
else
bufferProperties(*m_aStates.top().getCurrentBuffer(),
new RTFValue(lcl_getBookmarkProperties(nPos, aStr)), nullptr);
}
break;
case Destination::BOOKMARKEND:
{
if (&m_aStates.top().getDestinationText()
!= m_aStates.top().getCurrentDestinationText())
break; // not for nested group
OUString aStr = m_aStates.top().getCurrentDestinationText()->makeStringAndClear();
if (!m_aStates.top().getCurrentBuffer())
Mapper().props(new RTFReferenceProperties(
lcl_getBookmarkProperties(m_aBookmarks[aStr], aStr)));
else
bufferProperties(*m_aStates.top().getCurrentBuffer(),
new RTFValue(lcl_getBookmarkProperties(m_aBookmarks[aStr], aStr)),
nullptr);
}
break;
case Destination::INDEXENTRY:
case Destination::TOCENTRY:
{
if (&m_aStates.top().getDestinationText()
!= m_aStates.top().getCurrentDestinationText())
break; // not for nested group
OUString str(m_aStates.top().getCurrentDestinationText()->makeStringAndClear());
// dmapper expects this as a field, so let's fake something...
auto const field((Destination::INDEXENTRY == rState.getDestination())
? std::u16string_view(u"XE")
: std::u16string_view(u"TC"));
str = OUString::Concat(field) + " \"" + str.replaceAll("\"", "\\\"") + "\"";
singleChar(cFieldStart);
Mapper().utext(str.getStr(), str.getLength());
singleChar(cFieldSep, true);
// no result
singleChar(cFieldEnd);
}
break;
case Destination::FORMFIELDNAME:
{
if (&m_aStates.top().getDestinationText()
!= m_aStates.top().getCurrentDestinationText())
break; // not for nested group
auto pValue
= new RTFValue(m_aStates.top().getCurrentDestinationText()->makeStringAndClear());
m_aFormfieldSprms.set(NS_ooxml::LN_CT_FFData_name, pValue);
}
break;
case Destination::FORMFIELDLIST:
{
if (&m_aStates.top().getDestinationText()
!= m_aStates.top().getCurrentDestinationText())
break; // not for nested group
auto pValue
= new RTFValue(m_aStates.top().getCurrentDestinationText()->makeStringAndClear());
// OOXML puts these into a LN_CT_FFData_ddList but FFDataHandler should handle this too
m_aFormfieldSprms.set(NS_ooxml::LN_CT_FFDDList_listEntry, pValue,
RTFOverwrite::NO_APPEND);
}
break;
case Destination::DATAFIELD:
{
if (m_bFormField)
{
OUStringBuffer* pCurrentDestinationText
= m_aStates.top().getCurrentDestinationText();
if (&m_aStates.top().getDestinationText() != pCurrentDestinationText)
break; // not for nested group
OString aStr
= OUStringToOString(*pCurrentDestinationText, rState.getCurrentEncoding());
pCurrentDestinationText->setLength(0);
// decode hex dump
OStringBuffer aBuf;
int b = 0;
int count = 2;
for (int i = 0; i < aStr.getLength(); ++i)
{
char ch = aStr[i];
if (ch != 0x0d && ch != 0x0a)
{
b = b << 4;
sal_Int8 parsed = msfilter::rtfutil::AsHex(ch);
if (parsed == -1)
return RTFError::HEX_INVALID;
b += parsed;
count--;
if (!count)
{
aBuf.append(static_cast<char>(b));
count = 2;
b = 0;
}
}
}
aStr = aBuf.makeStringAndClear();
// ignore the first bytes
if (aStr.getLength() > 8)
aStr = aStr.copy(8);
// extract name
sal_Int32 nLength = aStr.toChar();
if (!aStr.isEmpty())
aStr = aStr.copy(1);
nLength = std::min(nLength, aStr.getLength());
OString aName = aStr.copy(0, nLength);
if (aStr.getLength() > nLength)
aStr = aStr.copy(nLength + 1); // zero-terminated string
else
aStr.clear();
// extract default text
nLength = aStr.toChar();
if (!aStr.isEmpty())
aStr = aStr.copy(1);
auto pNValue = new RTFValue(OStringToOUString(aName, rState.getCurrentEncoding()));
m_aFormfieldSprms.set(NS_ooxml::LN_CT_FFData_name, pNValue);
if (nLength > 0)
{
OString aDefaultText = aStr.copy(0, std::min(nLength, aStr.getLength()));
auto pDValue = new RTFValue(
OStringToOUString(aDefaultText, rState.getCurrentEncoding()));
m_aFormfieldSprms.set(NS_ooxml::LN_CT_FFTextInput_default, pDValue);
}
m_bFormField = false;
}
}
break;
case Destination::CREATIONTIME:
if (m_xDocumentProperties.is())
m_xDocumentProperties->setCreationDate(lcl_getDateTime(rState));
break;
case Destination::REVISIONTIME:
if (m_xDocumentProperties.is())
m_xDocumentProperties->setModificationDate(lcl_getDateTime(rState));
break;
case Destination::PRINTTIME:
if (m_xDocumentProperties.is())
m_xDocumentProperties->setPrintDate(lcl_getDateTime(rState));
break;
case Destination::AUTHOR:
if (&m_aStates.top().getDestinationText()
!= m_aStates.top().getCurrentDestinationText())
break; // not for nested group
if (m_xDocumentProperties.is())
m_xDocumentProperties->setAuthor(
m_aStates.top().getCurrentDestinationText()->makeStringAndClear());
break;
case Destination::KEYWORDS:
if (&m_aStates.top().getDestinationText()
!= m_aStates.top().getCurrentDestinationText())
break; // not for nested group
if (m_xDocumentProperties.is())
{
OUStringBuffer* pCurrentDestinationText
= m_aStates.top().getCurrentDestinationText();
m_xDocumentProperties->setKeywords(
comphelper::string::convertCommaSeparated(*pCurrentDestinationText));
pCurrentDestinationText->setLength(0);
}
break;
case Destination::COMMENT:
if (&m_aStates.top().getDestinationText()
!= m_aStates.top().getCurrentDestinationText())
break; // not for nested group
if (m_xDocumentProperties.is())
m_xDocumentProperties->setGenerator(
m_aStates.top().getCurrentDestinationText()->makeStringAndClear());
break;
case Destination::SUBJECT:
if (&m_aStates.top().getDestinationText()
!= m_aStates.top().getCurrentDestinationText())
break; // not for nested group
if (m_xDocumentProperties.is())
m_xDocumentProperties->setSubject(
m_aStates.top().getCurrentDestinationText()->makeStringAndClear());
break;
case Destination::TITLE:
{
if (&m_aStates.top().getDestinationText()
!= m_aStates.top().getCurrentDestinationText())
break; // not for nested group
if (m_xDocumentProperties.is())
m_xDocumentProperties->setTitle(
rState.getCurrentDestinationText()->makeStringAndClear());
}
break;
case Destination::DOCCOMM:
if (&m_aStates.top().getDestinationText()
!= m_aStates.top().getCurrentDestinationText())
break; // not for nested group
if (m_xDocumentProperties.is())
m_xDocumentProperties->setDescription(
m_aStates.top().getCurrentDestinationText()->makeStringAndClear());
break;
case Destination::OPERATOR:
case Destination::COMPANY:
{
if (&m_aStates.top().getDestinationText()
!= m_aStates.top().getCurrentDestinationText())
break; // not for nested group
OUString aName = rState.getDestination() == Destination::OPERATOR ? u"Operator"_ustr
: u"Company"_ustr;
uno::Any aValue(m_aStates.top().getCurrentDestinationText()->makeStringAndClear());
if (m_xDocumentProperties.is())
{
uno::Reference<beans::XPropertyContainer> xUserDefinedProperties
= m_xDocumentProperties->getUserDefinedProperties();
uno::Reference<beans::XPropertySet> xPropertySet(xUserDefinedProperties,
uno::UNO_QUERY);
uno::Reference<beans::XPropertySetInfo> xPropertySetInfo
= xPropertySet->getPropertySetInfo();
if (xPropertySetInfo->hasPropertyByName(aName))
xPropertySet->setPropertyValue(aName, aValue);
else
xUserDefinedProperties->addProperty(aName, beans::PropertyAttribute::REMOVABLE,
aValue);
}
}
break;
case Destination::OBJDATA:
{
if (&m_aStates.top().getDestinationText()
!= m_aStates.top().getCurrentDestinationText())
break; // not for nested group
RTFError eError = handleEmbeddedObject();
if (eError != RTFError::OK)
return eError;
}
break;
case Destination::OBJCLASS:
{
auto pValue
= new RTFValue(m_aStates.top().getCurrentDestinationText()->makeStringAndClear());
m_aOLEAttributes.set(NS_ooxml::LN_CT_OLEObject_ProgID, pValue);
break;
}
case Destination::OBJECT:
{
if (!m_bObject)
{
// if the object is in a special container we will use the \result
// element instead of the \objdata
// (see RTFKeyword::OBJECT in RTFDocumentImpl::dispatchDestination)
break;
}
RTFSprms aObjectSprms;
auto pOLEValue = new RTFValue(m_aOLEAttributes);
aObjectSprms.set(NS_ooxml::LN_OLEObject_OLEObject, pOLEValue);
RTFSprms aObjAttributes;
RTFSprms aObjSprms;
auto pValue = new RTFValue(m_aObjectAttributes, aObjectSprms);
aObjSprms.set(NS_ooxml::LN_object, pValue);
writerfilter::Reference<Properties>::Pointer_t pProperties
= new RTFReferenceProperties(std::move(aObjAttributes), std::move(aObjSprms));
uno::Reference<drawing::XShape> xShape;
RTFValue::Pointer_t pShape = m_aObjectAttributes.find(NS_ooxml::LN_shape);
OSL_ASSERT(pShape);
if (pShape)
pShape->getAny() >>= xShape;
if (xShape.is())
{
Mapper().startShape(xShape);
Mapper().props(pProperties);
Mapper().endShape();
}
m_aObjectAttributes.clear();
m_aOLEAttributes.clear();
m_bObject = false;
}
break;
case Destination::ANNOTATIONDATE:
{
OUStringBuffer* pCurrentDestinationText = m_aStates.top().getCurrentDestinationText();
if (&m_aStates.top().getDestinationText() != pCurrentDestinationText)
break; // not for nested group
OUString aStr(DTTM22OUString(o3tl::toInt32(*pCurrentDestinationText)));
pCurrentDestinationText->setLength(0);
auto pValue = new RTFValue(aStr);
RTFSprms aAnnAttributes;
aAnnAttributes.set(NS_ooxml::LN_CT_TrackChange_date, pValue);
writerfilter::Reference<Properties>::Pointer_t pProperties
= new RTFReferenceProperties(std::move(aAnnAttributes));
Mapper().props(pProperties);
}
break;
case Destination::ANNOTATIONAUTHOR:
if (&m_aStates.top().getDestinationText()
!= m_aStates.top().getCurrentDestinationText())
break; // not for nested group
m_aAuthor = m_aStates.top().getCurrentDestinationText()->makeStringAndClear();
break;
case Destination::ATNID:
if (&m_aStates.top().getDestinationText()
!= m_aStates.top().getCurrentDestinationText())
break; // not for nested group
m_aAuthorInitials = m_aStates.top().getCurrentDestinationText()->makeStringAndClear();
break;
case Destination::ANNOTATIONREFERENCESTART:
case Destination::ANNOTATIONREFERENCEEND:
{
if (&m_aStates.top().getDestinationText()
!= m_aStates.top().getCurrentDestinationText())
break; // not for nested group
OUString aStr = m_aStates.top().getCurrentDestinationText()->makeStringAndClear();
auto pValue = new RTFValue(aStr.toInt32());
RTFSprms aAttributes;
if (rState.getDestination() == Destination::ANNOTATIONREFERENCESTART)
aAttributes.set(NS_ooxml::LN_EG_RangeMarkupElements_commentRangeStart, pValue);
else
aAttributes.set(NS_ooxml::LN_EG_RangeMarkupElements_commentRangeEnd, pValue);
if (!m_aStates.top().getCurrentBuffer())
{
writerfilter::Reference<Properties>::Pointer_t pProperties
= new RTFReferenceProperties(std::move(aAttributes));
Mapper().props(pProperties);
}
else
{
auto const pValue2 = new RTFValue(aAttributes, RTFSprms());
bufferProperties(*m_aStates.top().getCurrentBuffer(), pValue2, nullptr);
}
}
break;
case Destination::ANNOTATIONREFERENCE:
{
if (&m_aStates.top().getDestinationText()
!= m_aStates.top().getCurrentDestinationText())
break; // not for nested group
OUString aStr = m_aStates.top().getCurrentDestinationText()->makeStringAndClear();
RTFSprms aAnnAttributes;
aAnnAttributes.set(NS_ooxml::LN_CT_Markup_id, new RTFValue(aStr.toInt32()));
Mapper().props(new RTFReferenceProperties(std::move(aAnnAttributes)));
}
break;
case Destination::FALT:
{
if (&m_aStates.top().getDestinationText()
!= m_aStates.top().getCurrentDestinationText())
break; // not for nested group
OUString aStr(m_aStates.top().getCurrentDestinationText()->makeStringAndClear());
auto pValue = new RTFValue(aStr);
rState.getTableSprms().set(NS_ooxml::LN_CT_Font_altName, pValue);
}
break;
case Destination::DRAWINGOBJECT:
if (m_aStates.top().getDrawingObject().getShape().is())
{
RTFDrawingObject& rDrawing = m_aStates.top().getDrawingObject();
const uno::Reference<drawing::XShape>& xShape(rDrawing.getShape());
const uno::Reference<beans::XPropertySet>& xPropertySet(rDrawing.getPropertySet());
uno::Reference<lang::XServiceInfo> xServiceInfo(xShape, uno::UNO_QUERY);
bool bTextFrame
= xServiceInfo->supportsService(u"com.sun.star.text.TextFrame"_ustr);
// The default is certainly not inline, but then what Word supports is just at-character.
xPropertySet->setPropertyValue(u"AnchorType"_ustr,
uno::Any(text::TextContentAnchorType_AT_CHARACTER));
if (bTextFrame)
{
xPropertySet->setPropertyValue(u"HoriOrientPosition"_ustr,
uno::Any(rDrawing.getLeft()));
xPropertySet->setPropertyValue(u"VertOrientPosition"_ustr,
uno::Any(rDrawing.getTop()));
}
else
{
xShape->setPosition(awt::Point(rDrawing.getLeft(), rDrawing.getTop()));
}
xShape->setSize(awt::Size(rDrawing.getRight(), rDrawing.getBottom()));
if (rDrawing.getHasLineColor())
{
uno::Any aLineColor(sal_uInt32((rDrawing.getLineColorR() << 16)
+ (rDrawing.getLineColorG() << 8)
+ rDrawing.getLineColorB()));
uno::Any aLineWidth;
RTFSdrImport::resolveLineColorAndWidth(bTextFrame, xPropertySet, aLineColor,
aLineWidth);
}
if (rDrawing.getHasFillColor())
xPropertySet->setPropertyValue(
u"FillColor"_ustr, uno::Any(sal_uInt32((rDrawing.getFillColorR() << 16)
+ (rDrawing.getFillColorG() << 8)
+ rDrawing.getFillColorB())));
else if (!bTextFrame)
// If there is no fill, the Word default is 100% transparency.
xPropertySet->setPropertyValue(u"FillTransparence"_ustr,
uno::Any(sal_Int32(100)));
RTFSdrImport::resolveFLine(xPropertySet, rDrawing.getFLine());
if (!m_aStates.top().getDrawingObject().getHadShapeText())
{
Mapper().startShape(xShape);
}
Mapper().endShape();
}
break;
case Destination::PICT:
// fdo#79319 ignore picture data if it's really a shape
if (!m_pSdrImport->isFakePict())
{
resolvePict(true, m_pSdrImport->getCurrentShape());
}
m_bNeedFinalPar = true;
break;
case Destination::SHAPE:
m_bNeedFinalPar = true;
m_bNeedCr = m_bNeedCrOrig;
// tdf#47036 insert paragraph break for graphic object inside text
// frame at start of document - TODO: the object may actually be
// anchored inside the text frame and this ends up putting the
// anchor in the body, but better than losing the shape...
if (rState.getFrame().hasProperties() && m_pSdrImport->isTextGraphicObject())
{
// parBreak() modifies m_aStates.top() so we can't apply resetFrame() directly on aState
resetFrame();
parBreak();
// Save this state for later use, so we only reset frame status only for the first shape inside a frame.
rState = m_aStates.top();
m_bNeedPap = true;
}
break;
case Destination::MOMATH:
{
m_aMathBuffer.appendClosingTag(M_TOKEN(oMath));
SvGlobalName aGlobalName(SO3_SM_CLASSID);
comphelper::EmbeddedObjectContainer aContainer;
OUString aName;
uno::Reference<embed::XEmbeddedObject> xObject
= aContainer.CreateEmbeddedObject(aGlobalName.GetByteSequence(), aName);
if (xObject) // rhbz#1766990 starmath might not be available
{
uno::Reference<util::XCloseable> xComponent(xObject->getComponent(),
uno::UNO_SET_THROW);
if (oox::FormulaImExportBase* pImport
= dynamic_cast<oox::FormulaImExportBase*>(xComponent.get()))
pImport->readFormulaOoxml(m_aMathBuffer);
auto pValue = new RTFValue(xObject);
RTFSprms aMathAttributes;
aMathAttributes.set(NS_ooxml::LN_starmath, pValue);
writerfilter::Reference<Properties>::Pointer_t pProperties
= new RTFReferenceProperties(std::move(aMathAttributes));
Mapper().props(pProperties);
}
m_aMathBuffer = oox::formulaimport::XmlStreamBuilder();
}
break;
case Destination::MR:
lcl_DestinationToMath(m_aStates.top().getCurrentDestinationText(), m_aMathBuffer,
m_bMathNor);
break;
case Destination::MF:
m_aMathBuffer.appendClosingTag(M_TOKEN(f));
break;
case Destination::MFPR:
m_aMathBuffer.appendClosingTag(M_TOKEN(fPr));
break;
case Destination::MCTRLPR:
m_aMathBuffer.appendClosingTag(M_TOKEN(ctrlPr));
break;
case Destination::MNUM:
m_aMathBuffer.appendClosingTag(M_TOKEN(num));
break;
case Destination::MDEN:
m_aMathBuffer.appendClosingTag(M_TOKEN(den));
break;
case Destination::MACC:
m_aMathBuffer.appendClosingTag(M_TOKEN(acc));
break;
case Destination::MACCPR:
m_aMathBuffer.appendClosingTag(M_TOKEN(accPr));
break;
case Destination::MCHR:
case Destination::MPOS:
case Destination::MVERTJC:
case Destination::MSTRIKEH:
case Destination::MDEGHIDE:
case Destination::MBEGCHR:
case Destination::MSEPCHR:
case Destination::MENDCHR:
case Destination::MSUBHIDE:
case Destination::MSUPHIDE:
case Destination::MTYPE:
case Destination::MGROW:
{
sal_Int32 nMathToken = 0;
switch (rState.getDestination())
{
case Destination::MCHR:
nMathToken = M_TOKEN(chr);
break;
case Destination::MPOS:
nMathToken = M_TOKEN(pos);
break;
case Destination::MVERTJC:
nMathToken = M_TOKEN(vertJc);
break;
case Destination::MSTRIKEH:
nMathToken = M_TOKEN(strikeH);
break;
case Destination::MDEGHIDE:
nMathToken = M_TOKEN(degHide);
break;
case Destination::MBEGCHR:
nMathToken = M_TOKEN(begChr);
break;
case Destination::MSEPCHR:
nMathToken = M_TOKEN(sepChr);
break;
case Destination::MENDCHR:
nMathToken = M_TOKEN(endChr);
break;
case Destination::MSUBHIDE:
nMathToken = M_TOKEN(subHide);
break;
case Destination::MSUPHIDE:
nMathToken = M_TOKEN(supHide);
break;
case Destination::MTYPE:
nMathToken = M_TOKEN(type);
break;
case Destination::MGROW:
nMathToken = M_TOKEN(grow);
break;
default:
break;
}
oox::formulaimport::XmlStream::AttributeList aAttribs;
aAttribs[M_TOKEN(val)]
= m_aStates.top().getCurrentDestinationText()->makeStringAndClear();
m_aMathBuffer.appendOpeningTag(nMathToken, aAttribs);
m_aMathBuffer.appendClosingTag(nMathToken);
}
break;
case Destination::ME:
m_aMathBuffer.appendClosingTag(M_TOKEN(e));
break;
case Destination::MBAR:
m_aMathBuffer.appendClosingTag(M_TOKEN(bar));
break;
case Destination::MBARPR:
m_aMathBuffer.appendClosingTag(M_TOKEN(barPr));
break;
case Destination::MD:
m_aMathBuffer.appendClosingTag(M_TOKEN(d));
break;
case Destination::MDPR:
m_aMathBuffer.appendClosingTag(M_TOKEN(dPr));
break;
case Destination::MFUNC:
m_aMathBuffer.appendClosingTag(M_TOKEN(func));
break;
case Destination::MFUNCPR:
m_aMathBuffer.appendClosingTag(M_TOKEN(funcPr));
break;
case Destination::MFNAME:
m_aMathBuffer.appendClosingTag(M_TOKEN(fName));
break;
case Destination::MLIMLOW:
m_aMathBuffer.appendClosingTag(M_TOKEN(limLow));
break;
case Destination::MLIMLOWPR:
m_aMathBuffer.appendClosingTag(M_TOKEN(limLowPr));
break;
case Destination::MLIM:
m_aMathBuffer.appendClosingTag(M_TOKEN(lim));
break;
case Destination::MM:
m_aMathBuffer.appendClosingTag(M_TOKEN(m));
break;
case Destination::MMPR:
m_aMathBuffer.appendClosingTag(M_TOKEN(mPr));
break;
case Destination::MMR:
m_aMathBuffer.appendClosingTag(M_TOKEN(mr));
break;
case Destination::MNARY:
m_aMathBuffer.appendClosingTag(M_TOKEN(nary));
break;
case Destination::MNARYPR:
m_aMathBuffer.appendClosingTag(M_TOKEN(naryPr));
break;
case Destination::MSUB:
m_aMathBuffer.appendClosingTag(M_TOKEN(sub));
break;
case Destination::MSUP:
m_aMathBuffer.appendClosingTag(M_TOKEN(sup));
break;
case Destination::MLIMUPP:
m_aMathBuffer.appendClosingTag(M_TOKEN(limUpp));
break;
case Destination::MLIMUPPPR:
m_aMathBuffer.appendClosingTag(M_TOKEN(limUppPr));
break;
case Destination::MGROUPCHR:
m_aMathBuffer.appendClosingTag(M_TOKEN(groupChr));
break;
case Destination::MGROUPCHRPR:
m_aMathBuffer.appendClosingTag(M_TOKEN(groupChrPr));
break;
case Destination::MBORDERBOX:
m_aMathBuffer.appendClosingTag(M_TOKEN(borderBox));
break;
case Destination::MBORDERBOXPR:
m_aMathBuffer.appendClosingTag(M_TOKEN(borderBoxPr));
break;
case Destination::MRAD:
m_aMathBuffer.appendClosingTag(M_TOKEN(rad));
break;
case Destination::MRADPR:
m_aMathBuffer.appendClosingTag(M_TOKEN(radPr));
break;
case Destination::MDEG:
m_aMathBuffer.appendClosingTag(M_TOKEN(deg));
break;
case Destination::MSSUB:
m_aMathBuffer.appendClosingTag(M_TOKEN(sSub));
break;
case Destination::MSSUBPR:
m_aMathBuffer.appendClosingTag(M_TOKEN(sSubPr));
break;
case Destination::MSSUP:
m_aMathBuffer.appendClosingTag(M_TOKEN(sSup));
break;
case Destination::MSSUPPR:
m_aMathBuffer.appendClosingTag(M_TOKEN(sSupPr));
break;
case Destination::MSSUBSUP:
m_aMathBuffer.appendClosingTag(M_TOKEN(sSubSup));
break;
case Destination::MSSUBSUPPR:
m_aMathBuffer.appendClosingTag(M_TOKEN(sSubSupPr));
break;
case Destination::MSPRE:
m_aMathBuffer.appendClosingTag(M_TOKEN(sPre));
break;
case Destination::MSPREPR:
m_aMathBuffer.appendClosingTag(M_TOKEN(sPrePr));
break;
case Destination::MBOX:
m_aMathBuffer.appendClosingTag(M_TOKEN(box));
break;
case Destination::MEQARR:
m_aMathBuffer.appendClosingTag(M_TOKEN(eqArr));
break;
case Destination::SHAPEGROUP:
if (rState.getCreatedShapeGroup())
m_pSdrImport->popParent();
break;
case Destination::PROPNAME:
if (&m_aStates.top().getDestinationText()
!= m_aStates.top().getCurrentDestinationText())
break; // not for nested group
rState.setPropName(m_aStates.top().getCurrentDestinationText()->makeStringAndClear());
break;
case Destination::STATICVAL:
if (&m_aStates.top().getDestinationText()
!= m_aStates.top().getCurrentDestinationText())
break; // not for nested group
if (m_xDocumentProperties.is())
{
// Find out what is the key, value type and value we want to set.
uno::Reference<beans::XPropertyContainer> xPropertyContainer
= m_xDocumentProperties->getUserDefinedProperties();
const OUString& rKey = m_aStates.top().getPropName();
OUString aStaticVal
= m_aStates.top().getCurrentDestinationText()->makeStringAndClear();
uno::Any aAny;
if (m_aStates.top().getPropType() == cppu::UnoType<OUString>::get())
aAny <<= aStaticVal;
else if (m_aStates.top().getPropType() == cppu::UnoType<sal_Int32>::get())
aAny <<= aStaticVal.toInt32();
else if (m_aStates.top().getPropType() == cppu::UnoType<bool>::get())
aAny <<= aStaticVal.toBoolean();
else if (m_aStates.top().getPropType() == cppu::UnoType<util::DateTime>::get())
aAny <<= getDateTimeFromUserProp(aStaticVal);
else if (m_aStates.top().getPropType() == cppu::UnoType<double>::get())
aAny <<= aStaticVal.toDouble();
xPropertyContainer->addProperty(rKey, beans::PropertyAttribute::REMOVABLE, aAny);
}
break;
case Destination::USERPROPS:
{
// These are the imported properties.
uno::Reference<document::XDocumentProperties> xDocumentProperties
= m_xDocumentProperties;
// These are the real document properties.
if (m_xDstDoc)
m_xDocumentProperties = m_xDstDoc->getDocumentProperties();
if (m_xDocumentProperties.is())
{
if (!m_bIsNewDoc)
{
// Check classification.
if (!SfxClassificationHelper::ShowPasteInfo(SfxClassificationHelper::CheckPaste(
xDocumentProperties, m_xDocumentProperties)))
return RTFError::CLASSIFICATION;
}
uno::Reference<beans::XPropertyContainer> xClipboardPropertyContainer
= xDocumentProperties->getUserDefinedProperties();
uno::Reference<beans::XPropertyContainer> xDocumentPropertyContainer
= m_xDocumentProperties->getUserDefinedProperties();
uno::Reference<beans::XPropertySet> xClipboardPropertySet(
xClipboardPropertyContainer, uno::UNO_QUERY);
uno::Reference<beans::XPropertySet> xDocumentPropertySet(xDocumentPropertyContainer,
uno::UNO_QUERY);
const uno::Sequence<beans::Property> aClipboardProperties
= xClipboardPropertySet->getPropertySetInfo()->getProperties();
uno::Sequence<beans::Property> aDocumentProperties
= xDocumentPropertySet->getPropertySetInfo()->getProperties();
for (const beans::Property& rProperty : aClipboardProperties)
{
const OUString& rKey = rProperty.Name;
uno::Any aValue = xClipboardPropertySet->getPropertyValue(rKey);
try
{
if (lcl_containsProperty(aDocumentProperties, rKey))
{
// When pasting, don't update existing properties.
if (!m_bIsNewDoc)
xDocumentPropertySet->setPropertyValue(rKey, aValue);
}
else
xDocumentPropertyContainer->addProperty(
rKey, beans::PropertyAttribute::REMOVABLE, aValue);
}
catch (const uno::Exception&)
{
TOOLS_WARN_EXCEPTION("writerfilter.rtf", "failed to set property " << rKey);
}
}
}
}
break;
default:
break;
}
return RTFError::OK;
}
void RTFDocumentImpl::afterPopState(RTFParserState& rState)
{
// list table
switch (rState.getDestination())
{
case Destination::LISTENTRY:
{
auto pValue = new RTFValue(rState.getTableAttributes(), rState.getTableSprms());
m_aListTableSprms.set(NS_ooxml::LN_CT_Numbering_abstractNum, pValue,
RTFOverwrite::NO_APPEND);
m_aListTable[rState.getCurrentListIndex()] = pValue;
m_nListLevel = -1;
m_aInvalidListTableFirstIndents[rState.getCurrentListIndex()]
= m_aInvalidListLevelFirstIndents;
m_aInvalidListLevelFirstIndents.clear();
}
break;
case Destination::PARAGRAPHNUMBERING:
{
RTFValue::Pointer_t pIdValue
= rState.getTableAttributes().find(NS_ooxml::LN_CT_AbstractNum_nsid);
if (pIdValue && !m_aStates.empty())
{
// Abstract numbering
RTFSprms aLeveltextAttributes;
OUString aTextValue;
RTFValue::Pointer_t pTextBefore
= rState.getTableAttributes().find(NS_ooxml::LN_CT_LevelText_val);
if (pTextBefore)
aTextValue += pTextBefore->getString();
aTextValue += "%1";
RTFValue::Pointer_t pTextAfter
= rState.getTableAttributes().find(NS_ooxml::LN_CT_LevelSuffix_val);
if (pTextAfter)
aTextValue += pTextAfter->getString();
auto pTextValue = new RTFValue(aTextValue);
aLeveltextAttributes.set(NS_ooxml::LN_CT_LevelText_val, pTextValue);
RTFSprms aLevelAttributes;
RTFSprms aLevelSprms;
auto pIlvlValue = new RTFValue(0);
aLevelAttributes.set(NS_ooxml::LN_CT_Lvl_ilvl, pIlvlValue);
RTFValue::Pointer_t pFmtValue
= rState.getTableSprms().find(NS_ooxml::LN_CT_Lvl_numFmt);
if (pFmtValue)
aLevelSprms.set(NS_ooxml::LN_CT_Lvl_numFmt, pFmtValue);
RTFValue::Pointer_t pStartatValue
= rState.getTableSprms().find(NS_ooxml::LN_CT_Lvl_start);
if (pStartatValue)
aLevelSprms.set(NS_ooxml::LN_CT_Lvl_start, pStartatValue);
auto pLeveltextValue = new RTFValue(aLeveltextAttributes);
aLevelSprms.set(NS_ooxml::LN_CT_Lvl_lvlText, pLeveltextValue);
RTFValue::Pointer_t pRunProps
= rState.getTableSprms().find(NS_ooxml::LN_CT_Lvl_rPr);
if (pRunProps)
aLevelSprms.set(NS_ooxml::LN_CT_Lvl_rPr, pRunProps);
RTFSprms aAbstractAttributes;
RTFSprms aAbstractSprms;
aAbstractAttributes.set(NS_ooxml::LN_CT_AbstractNum_abstractNumId, pIdValue);
auto pLevelValue = new RTFValue(aLevelAttributes, aLevelSprms);
aAbstractSprms.set(NS_ooxml::LN_CT_AbstractNum_lvl, pLevelValue,
RTFOverwrite::NO_APPEND);
RTFSprms aListTableSprms;
auto pAbstractValue = new RTFValue(aAbstractAttributes, aAbstractSprms);
// It's important that Numbering_abstractNum and Numbering_num never overwrites previous values.
aListTableSprms.set(NS_ooxml::LN_CT_Numbering_abstractNum, pAbstractValue,
RTFOverwrite::NO_APPEND);
// Numbering
RTFSprms aNumberingAttributes;
RTFSprms aNumberingSprms;
aNumberingAttributes.set(NS_ooxml::LN_CT_AbstractNum_nsid, pIdValue);
aNumberingSprms.set(NS_ooxml::LN_CT_Num_abstractNumId, pIdValue);
auto pNumberingValue = new RTFValue(aNumberingAttributes, aNumberingSprms);
aListTableSprms.set(NS_ooxml::LN_CT_Numbering_num, pNumberingValue,
RTFOverwrite::NO_APPEND);
// Table
RTFSprms aListTableAttributes;
writerfilter::Reference<Properties>::Pointer_t pProp = new RTFReferenceProperties(
std::move(aListTableAttributes), std::move(aListTableSprms));
RTFReferenceTable::Entries_t aListTableEntries;
aListTableEntries.insert(std::make_pair(0, pProp));
writerfilter::Reference<Table>::Pointer_t const pTable(
new RTFReferenceTable(std::move(aListTableEntries)));
Mapper().table(NS_ooxml::LN_NUMBERING, pTable);
// Use it
putNestedSprm(m_aStates.top().getParagraphSprms(), NS_ooxml::LN_CT_PPrBase_numPr,
NS_ooxml::LN_CT_NumPr_ilvl, pIlvlValue, RTFOverwrite::YES_PREPEND);
putNestedSprm(m_aStates.top().getParagraphSprms(), NS_ooxml::LN_CT_PPrBase_numPr,
NS_ooxml::LN_CT_NumPr_numId, pIdValue, RTFOverwrite::YES_PREPEND);
}
}
break;
case Destination::PARAGRAPHNUMBERING_TEXTAFTER:
if (!m_aStates.empty())
{
// FIXME: don't use pDestinationText, points to popped state
auto pValue = new RTFValue(rState.getDestinationText().makeStringAndClear(), true);
m_aStates.top().getTableAttributes().set(NS_ooxml::LN_CT_LevelSuffix_val, pValue);
}
break;
case Destination::PARAGRAPHNUMBERING_TEXTBEFORE:
if (!m_aStates.empty())
{
// FIXME: don't use pDestinationText, points to popped state
auto pValue = new RTFValue(rState.getDestinationText().makeStringAndClear(), true);
m_aStates.top().getTableAttributes().set(NS_ooxml::LN_CT_LevelText_val, pValue);
}
break;
case Destination::LISTNAME:
break;
case Destination::LISTLEVEL:
if (!m_aStates.empty())
{
auto pInnerValue = new RTFValue(m_aStates.top().getListLevelNum()++);
rState.getTableAttributes().set(NS_ooxml::LN_CT_Lvl_ilvl, pInnerValue);
auto pValue = new RTFValue(rState.getTableAttributes(), rState.getTableSprms());
if (m_aStates.top().getDestination() != Destination::LFOLEVEL)
m_aStates.top().getListLevelEntries().set(NS_ooxml::LN_CT_AbstractNum_lvl,
pValue, RTFOverwrite::NO_APPEND);
else
m_aStates.top().getTableSprms().set(NS_ooxml::LN_CT_NumLvl_lvl, pValue);
}
break;
case Destination::LFOLEVEL:
if (!m_aStates.empty())
{
auto pInnerValue = new RTFValue(m_aStates.top().getListLevelNum()++);
rState.getTableAttributes().set(NS_ooxml::LN_CT_NumLvl_ilvl, pInnerValue);
auto pValue = new RTFValue(rState.getTableAttributes(), rState.getTableSprms());
m_aStates.top().getTableSprms().set(NS_ooxml::LN_CT_Num_lvlOverride, pValue,
RTFOverwrite::NO_APPEND);
}
break;
// list override table
case Destination::LISTOVERRIDEENTRY:
if (!m_aStates.empty())
{
if (m_aStates.top().getDestination() == Destination::LISTOVERRIDEENTRY)
{
// copy properties upwards so upper popState() inserts it
m_aStates.top().getTableAttributes() = rState.getTableAttributes();
m_aStates.top().getTableSprms() = rState.getTableSprms();
}
else
{
auto pValue = new RTFValue(rState.getTableAttributes(), rState.getTableSprms());
m_aListTableSprms.set(NS_ooxml::LN_CT_Numbering_num, pValue,
RTFOverwrite::NO_APPEND);
m_aListOverrideTable[rState.getCurrentListOverrideIndex()]
= rState.getCurrentListIndex();
}
}
break;
case Destination::LEVELTEXT:
if (!m_aStates.empty())
{
auto pValue = new RTFValue(rState.getTableAttributes());
m_aStates.top().getTableSprms().set(NS_ooxml::LN_CT_Lvl_lvlText, pValue);
}
break;
case Destination::LEVELNUMBERS:
if (!m_aStates.empty())
{
m_aStates.top().getTableSprms() = rState.getTableSprms();
if (m_aStates.top().getDestination() == Destination::LEVELNUMBERS
|| m_aStates.top().getDestination() == Destination::LISTLEVEL)
// Parent state is level number or list level, current state is
// level numbers: mark parent as invalid as well if necessary.
m_aStates.top().setLevelNumbersValid(rState.getLevelNumbersValid());
}
break;
case Destination::FIELDINSTRUCTION:
if (!m_aStates.empty())
m_aStates.top().setFieldStatus(RTFFieldStatus::INSTRUCTION);
break;
case Destination::FIELDRESULT:
if (!m_aStates.empty())
m_aStates.top().setFieldStatus(RTFFieldStatus::RESULT);
break;
case Destination::FIELD:
if (rState.getFieldStatus() == RTFFieldStatus::INSTRUCTION)
singleChar(cFieldEnd);
break;
case Destination::DOCVAR:
if (!m_aStates.empty())
{
OUString docvar(rState.getDocVar());
if (m_aStates.top().getDocVarName().isEmpty())
{
m_aStates.top().setDocVarName(docvar);
}
else
{
uno::Reference<beans::XPropertySet> xMaster(
m_xDstDoc->createInstance(u"com.sun.star.text.FieldMaster.User"_ustr),
uno::UNO_QUERY_THROW);
xMaster->setPropertyValue(u"Name"_ustr,
uno::Any(m_aStates.top().getDocVarName()));
uno::Reference<text::XDependentTextField> xField(
m_xDstDoc->createInstance(u"com.sun.star.text.TextField.User"_ustr),
uno::UNO_QUERY);
xField->attachTextFieldMaster(xMaster);
xField->getTextFieldMaster()->setPropertyValue(u"Content"_ustr,
uno::Any(docvar));
m_aStates.top().clearDocVarName();
}
}
break;
case Destination::SHAPEPROPERTYVALUEPICT:
if (!m_aStates.empty())
{
m_aStates.top().getPicture() = rState.getPicture();
// both \sp and \sv are destinations, copy the text up-ward for later
m_aStates.top().getDestinationText() = rState.getDestinationText();
}
break;
case Destination::FALT:
if (!m_aStates.empty())
m_aStates.top().getTableSprms() = rState.getTableSprms();
break;
case Destination::SHAPEPROPERTYNAME:
case Destination::SHAPEPROPERTYVALUE:
case Destination::SHAPEPROPERTY:
if (!m_aStates.empty())
{
m_aStates.top().getShape() = rState.getShape();
m_aStates.top().getPicture() = rState.getPicture();
m_aStates.top().getCharacterAttributes() = rState.getCharacterAttributes();
}
break;
case Destination::SHAPEINSTRUCTION:
if (!m_aStates.empty()
&& m_aStates.top().getDestination() == Destination::SHAPEINSTRUCTION)
{
// Shape instruction inside other shape instruction: just copy new shape settings:
// it will be resolved on end of topmost shape instruction destination
m_aStates.top().getShape() = rState.getShape();
m_aStates.top().getPicture() = rState.getPicture();
m_aStates.top().getCharacterSprms() = rState.getCharacterSprms();
m_aStates.top().getCharacterAttributes() = rState.getCharacterAttributes();
}
break;
case Destination::FLYMAINCONTENT:
case Destination::SHPPICT:
case Destination::SHAPE:
if (!m_aStates.empty())
{
m_aStates.top().getFrame() = rState.getFrame();
if (rState.getDestination() == Destination::SHPPICT
&& m_aStates.top().getDestination() == Destination::LISTPICTURE)
{
RTFSprms aAttributes;
aAttributes.set(NS_ooxml::LN_CT_NumPicBullet_numPicBulletId,
new RTFValue(m_nListPictureId++));
RTFSprms aSprms;
// Dummy value, real picture is already sent to dmapper.
aSprms.set(NS_ooxml::LN_CT_NumPicBullet_pict, new RTFValue(0));
auto pValue = new RTFValue(aAttributes, aSprms);
m_aListTableSprms.set(NS_ooxml::LN_CT_Numbering_numPicBullet, pValue,
RTFOverwrite::NO_APPEND);
}
}
break;
case Destination::SHAPETEXT:
if (!m_aStates.empty())
{
// If we're leaving the shapetext group (it may have nested ones) and this is a shape, not an old drawingobject.
if (m_aStates.top().getDestination() != Destination::SHAPETEXT
&& !m_aStates.top().getDrawingObject().getHadShapeText())
{
m_aStates.top().setHadShapeText(true);
if (!m_aStates.top().getCurrentBuffer())
m_pSdrImport->close();
else
m_aStates.top().getCurrentBuffer()->emplace_back(BUFFER_ENDSHAPE, nullptr,
nullptr);
}
// It's allowed to declare these inside the shape text, and they
// are expected to have an effect for the whole shape.
if (rState.getDrawingObject().getLeft())
m_aStates.top().getDrawingObject().setLeft(rState.getDrawingObject().getLeft());
if (rState.getDrawingObject().getTop())
m_aStates.top().getDrawingObject().setTop(rState.getDrawingObject().getTop());
if (rState.getDrawingObject().getRight())
m_aStates.top().getDrawingObject().setRight(
rState.getDrawingObject().getRight());
if (rState.getDrawingObject().getBottom())
m_aStates.top().getDrawingObject().setBottom(
rState.getDrawingObject().getBottom());
}
break;
case Destination::PROPNAME:
if (m_aStates.top().getDestination() == Destination::USERPROPS)
m_aStates.top().setPropName(rState.getPropName());
break;
default:
{
if (!m_aStates.empty() && m_aStates.top().getDestination() == Destination::PICT)
m_aStates.top().getPicture() = rState.getPicture();
}
break;
}
}
RTFError RTFDocumentImpl::popState()
{
//SAL_INFO("writerfilter", __func__ << " before pop: m_pTokenizer->getGroup() " << m_pTokenizer->getGroup() <<
// ", dest state: " << m_aStates.top().eDestination);
checkUnicode(/*bUnicode =*/true, /*bHex =*/true);
RTFParserState aState(m_aStates.top());
m_bWasInFrame = aState.getFrame().hasProperties();
// dmapper expects some content in header/footer, so if there would be nothing, add an empty paragraph.
if (m_pTokenizer->getGroup() == 1 && m_bFirstRun)
{
switch (m_nStreamType)
{
case NS_ooxml::LN_headerl:
case NS_ooxml::LN_headerr:
case NS_ooxml::LN_headerf:
case NS_ooxml::LN_footerl:
case NS_ooxml::LN_footerr:
case NS_ooxml::LN_footerf:
dispatchSymbol(RTFKeyword::PAR);
break;
}
}
RTFError eError = beforePopState(aState);
if (eError != RTFError::OK)
return eError;
// See if we need to end a track change
if (aState.getStartedTrackchange())
{
RTFSprms aTCSprms;
auto pValue = new RTFValue(0);
aTCSprms.set(NS_ooxml::LN_endtrackchange, pValue);
if (!m_aStates.top().getCurrentBuffer())
Mapper().props(new RTFReferenceProperties(RTFSprms(), std::move(aTCSprms)));
else
bufferProperties(*m_aStates.top().getCurrentBuffer(),
new RTFValue(RTFSprms(), aTCSprms), nullptr);
}
// This is the end of the doc, see if we need to close the last section.
if (m_pTokenizer->getGroup() == 1 && !m_bFirstRun)
{
// \par means an empty paragraph at the end of footnotes/endnotes, but
// not in case of other substreams, like headers.
if (m_bNeedCr && m_nStreamType != NS_ooxml::LN_footnote
&& m_nStreamType != NS_ooxml::LN_endnote)
{
if (!m_bIsNewDoc)
{
// Make sure all the paragraph settings are set, but do not add next paragraph
Mapper().markLastParagraph();
}
dispatchSymbol(RTFKeyword::PAR);
}
if (m_bNeedSect) // may be set by dispatchSymbol above!
sectBreak(true);
else if (!m_pSuperstream)
{
Mapper().markLastSectionGroup(); // ensure it's set for \par below
}
if (m_bNeedPar && !m_pSuperstream)
{
assert(!m_bNeedSect);
dispatchSymbol(RTFKeyword::PAR);
m_bNeedSect = false; // reset - m_bNeedPar was set for \sect at
// end of doc so don't need another one
}
}
m_aStates.pop();
m_pTokenizer->popGroup();
afterPopState(aState);
if (aState.getCurrentBuffer() == &m_aSuperBuffer)
{
OSL_ASSERT(!m_aStates.empty() && m_aStates.top().getCurrentBuffer() == nullptr);
if (!m_aSuperBuffer.empty())
replayBuffer(m_aSuperBuffer, nullptr, nullptr);
}
if (!m_aStates.empty() && m_aStates.top().getTableRowWidthAfter() > 0
&& aState.getTableRowWidthAfter() == 0)
// An RTFKeyword::ROW in the inner group already parsed nTableRowWidthAfter,
// don't do it again in the outer state later.
m_aStates.top().setTableRowWidthAfter(0);
if (m_nResetBreakOnSectBreak != RTFKeyword::invalid && !m_aStates.empty())
{
// Section break type created for \page still has an effect in the
// outer state as well.
RTFValue::Pointer_t pType
= aState.getSectionSprms().find(NS_ooxml::LN_EG_SectPrContents_type);
if (pType)
m_aStates.top().getSectionSprms().set(NS_ooxml::LN_EG_SectPrContents_type, pType);
}
return RTFError::OK;
}
RTFError RTFDocumentImpl::handleEmbeddedObject()
{
OUStringBuffer* pCurrentDestinationText = m_aStates.top().getCurrentDestinationText();
OString aStr = OUStringToOString(*pCurrentDestinationText, RTL_TEXTENCODING_ASCII_US);
pCurrentDestinationText->setLength(0);
std::unique_ptr<SvStream> pStream(new SvMemoryStream());
if (!msfilter::rtfutil::ExtractOLE2FromObjdata(aStr, *pStream))
return RTFError::HEX_INVALID;
uno::Reference<io::XInputStream> xInputStream(
new utl::OSeekableInputStreamWrapper(pStream.release(), /*_bOwner=*/true));
m_aOLEAttributes.set(NS_ooxml::LN_inputstream, new RTFValue(xInputStream));
return RTFError::OK;
}
bool RTFDocumentImpl::isInBackground() { return m_aStates.top().getInBackground(); }
RTFInternalState RTFDocumentImpl::getInternalState() { return m_aStates.top().getInternalState(); }
void RTFDocumentImpl::setInternalState(RTFInternalState nInternalState)
{
m_aStates.top().setInternalState(nInternalState);
}
Destination RTFDocumentImpl::getDestination() { return m_aStates.top().getDestination(); }
void RTFDocumentImpl::setDestination(Destination eDestination)
{
m_aStates.top().setDestination(eDestination);
}
// this is a questionably named method that is used only in a very special
// situation where it looks like the "current" buffer is needed?
void RTFDocumentImpl::setDestinationText(std::u16string_view rString)
{
m_aStates.top().getDestinationText().setLength(0);
m_aStates.top().getDestinationText().append(rString);
}
bool RTFDocumentImpl::getSkipUnknown() { return m_bSkipUnknown; }
void RTFDocumentImpl::setSkipUnknown(bool bSkipUnknown) { m_bSkipUnknown = bSkipUnknown; }
static auto FilterControlChars(Destination const destination, OUString const& rString) -> OUString
{
if (destination == Destination::LEVELNUMBERS || destination == Destination::LEVELTEXT)
{ // control characters are magic here!
return rString;
}
OUStringBuffer buf(rString.getLength());
for (sal_Int32 i = 0; i < rString.getLength(); ++i)
{
sal_Unicode const ch(rString[i]);
if (!linguistic::IsControlChar(ch) || ch == '\r' || ch == '\n' || ch == '\t')
{
buf.append(ch);
}
else
{
SAL_INFO("writerfilter.rtf", "filtering control character");
}
}
return buf.makeStringAndClear();
}
void RTFDocumentImpl::checkUnicode(bool bUnicode, bool bHex)
{
if (bUnicode && !m_aUnicodeBuffer.isEmpty())
{
OUString aString = m_aUnicodeBuffer.toString();
m_aUnicodeBuffer.setLength(0);
aString = FilterControlChars(m_aStates.top().getDestination(), aString);
text(aString);
}
if (bHex && !m_aHexBuffer.isEmpty())
{
rtl_TextEncoding nEncoding = m_aStates.top().getCurrentEncoding();
if (nEncoding == RTL_TEXTENCODING_SYMBOL
&& (m_aStates.top().getDestination() == Destination::FONTENTRY
|| (m_aStates.size() > 1
&& m_aStates[m_aStates.size() - 2].getDestination()
== Destination::FIELDINSTRUCTION)))
nEncoding = RTL_TEXTENCODING_MS_1252;
OUString aString = OStringToOUString(m_aHexBuffer, nEncoding);
m_aHexBuffer.setLength(0);
aString = FilterControlChars(m_aStates.top().getDestination(), aString);
text(aString);
}
}
RTFParserState::RTFParserState(RTFDocumentImpl* pDocumentImpl)
: m_pDocumentImpl(pDocumentImpl)
, m_nInternalState(RTFInternalState::NORMAL)
, m_eDestination(Destination::NORMAL)
, m_eFieldStatus(RTFFieldStatus::NONE)
, m_bFieldLocked(false)
, m_nBorderState(RTFBorderState::NONE)
, m_nCurrentEncoding(rtl_getTextEncodingFromWindowsCharset(0))
, m_nUc(1)
, m_nCharsToSkip(0)
, m_nBinaryToRead(0)
, m_nListLevelNum(0)
, m_bLevelNumbersValid(true)
, m_aFrame(this)
, m_eRunType(RunType::NONE)
, m_nYear(0)
, m_nMonth(0)
, m_nDay(0)
, m_nHour(0)
, m_nMinute(0)
, m_pCurrentDestinationText(nullptr)
, m_nCurrentStyleIndex(0)
, m_nCurrentCharacterStyleIndex(-1)
, m_pCurrentBuffer(nullptr)
, m_bInListpicture(false)
, m_bInBackground(false)
, m_bHadShapeText(false)
, m_bInShapeGroup(false)
, m_bInShape(false)
, m_bCreatedShapeGroup(false)
, m_bStartedTrackchange(false)
, m_nTableRowWidthAfter(0)
{
}
void RTFDocumentImpl::resetFrame() { m_aStates.top().getFrame() = RTFFrame(&m_aStates.top()); }
void RTFDocumentImpl::bufferProperties(RTFBuffer_t& rBuffer, const RTFValue::Pointer_t& pValue,
const tools::SvRef<TableRowBuffer>& pTableProperties,
Id const nStyleType)
{
rBuffer.emplace_back(BUFFER_SETSTYLE, new RTFValue(m_aStates.top().getCurrentStyleIndex()),
nullptr);
assert(nStyleType == 0 || nStyleType == NS_ooxml::LN_Value_ST_StyleType_character);
rBuffer.emplace_back(nStyleType == NS_ooxml::LN_Value_ST_StyleType_character ? BUFFER_PROPS_CHAR
: BUFFER_PROPS,
pValue, pTableProperties);
}
RTFShape::RTFShape() = default;
RTFDrawingObject::RTFDrawingObject() = default;
RTFFrame::RTFFrame(RTFParserState* pParserState)
: m_pDocumentImpl(pParserState->getDocumentImpl())
, m_nX(0)
, m_nY(0)
, m_nW(0)
, m_nH(0)
, m_nHoriPadding(0)
, m_nVertPadding(0)
, m_nHoriAlign(0)
, m_nHoriAnchor(0)
, m_nVertAlign(0)
, m_nVertAnchor(0)
, m_nHRule(NS_ooxml::LN_Value_doc_ST_HeightRule_auto)
{
}
void RTFFrame::setSprm(Id nId, Id nValue)
{
switch (nId)
{
case NS_ooxml::LN_CT_FramePr_w:
m_nW = nValue;
break;
case NS_ooxml::LN_CT_FramePr_h:
m_nH = nValue;
break;
case NS_ooxml::LN_CT_FramePr_x:
m_nX = nValue;
break;
case NS_ooxml::LN_CT_FramePr_y:
m_nY = nValue;
break;
case NS_ooxml::LN_CT_FramePr_hSpace:
m_nHoriPadding = nValue;
break;
case NS_ooxml::LN_CT_FramePr_vSpace:
m_nVertPadding = nValue;
break;
case NS_ooxml::LN_CT_FramePr_xAlign:
m_nHoriAlign = nValue;
break;
case NS_ooxml::LN_CT_FramePr_hAnchor:
m_nHoriAnchor = nValue;
break;
case NS_ooxml::LN_CT_FramePr_yAlign:
m_nVertAlign = nValue;
break;
case NS_ooxml::LN_CT_FramePr_vAnchor:
m_nVertAnchor = nValue;
break;
case NS_ooxml::LN_CT_FramePr_wrap:
m_oWrap = nValue;
break;
default:
break;
}
if (m_pDocumentImpl->getFirstRun() && !m_pDocumentImpl->isStyleSheetImport() && hasProperties())
{
m_pDocumentImpl->checkFirstRun();
m_pDocumentImpl->setNeedPar(false);
}
}
RTFSprms RTFFrame::getSprms()
{
RTFSprms sprms;
static const Id pNames[]
= { NS_ooxml::LN_CT_FramePr_x, NS_ooxml::LN_CT_FramePr_y,
NS_ooxml::LN_CT_FramePr_hRule, // Make sure nHRule is processed before nH
NS_ooxml::LN_CT_FramePr_h, NS_ooxml::LN_CT_FramePr_w,
NS_ooxml::LN_CT_FramePr_hSpace, NS_ooxml::LN_CT_FramePr_vSpace,
NS_ooxml::LN_CT_FramePr_hAnchor, NS_ooxml::LN_CT_FramePr_vAnchor,
NS_ooxml::LN_CT_FramePr_xAlign, NS_ooxml::LN_CT_FramePr_yAlign,
NS_ooxml::LN_CT_FramePr_wrap, NS_ooxml::LN_CT_FramePr_dropCap,
NS_ooxml::LN_CT_FramePr_lines };
for (Id nId : pNames)
{
RTFValue::Pointer_t pValue;
switch (nId)
{
case NS_ooxml::LN_CT_FramePr_x:
if (m_nX != 0)
pValue = new RTFValue(m_nX);
break;
case NS_ooxml::LN_CT_FramePr_y:
if (m_nY != 0)
pValue = new RTFValue(m_nY);
break;
case NS_ooxml::LN_CT_FramePr_h:
if (m_nH != 0)
{
if (m_nHRule == NS_ooxml::LN_Value_doc_ST_HeightRule_exact)
pValue = new RTFValue(-m_nH); // The negative value just sets nHRule
else
pValue = new RTFValue(m_nH);
}
break;
case NS_ooxml::LN_CT_FramePr_w:
if (m_nW != 0)
pValue = new RTFValue(m_nW);
break;
case NS_ooxml::LN_CT_FramePr_hSpace:
if (m_nHoriPadding != 0)
pValue = new RTFValue(m_nHoriPadding);
break;
case NS_ooxml::LN_CT_FramePr_vSpace:
if (m_nVertPadding != 0)
pValue = new RTFValue(m_nVertPadding);
break;
case NS_ooxml::LN_CT_FramePr_hAnchor:
{
if (m_nHoriAnchor == 0)
m_nHoriAnchor = NS_ooxml::LN_Value_doc_ST_HAnchor_text;
pValue = new RTFValue(m_nHoriAnchor);
}
break;
case NS_ooxml::LN_CT_FramePr_vAnchor:
{
if (m_nVertAnchor == 0)
m_nVertAnchor = NS_ooxml::LN_Value_doc_ST_VAnchor_margin;
pValue = new RTFValue(m_nVertAnchor);
}
break;
case NS_ooxml::LN_CT_FramePr_xAlign:
pValue = new RTFValue(m_nHoriAlign);
break;
case NS_ooxml::LN_CT_FramePr_yAlign:
pValue = new RTFValue(m_nVertAlign);
break;
case NS_ooxml::LN_CT_FramePr_hRule:
{
if (m_nH < 0)
m_nHRule = NS_ooxml::LN_Value_doc_ST_HeightRule_exact;
else if (m_nH > 0)
m_nHRule = NS_ooxml::LN_Value_doc_ST_HeightRule_atLeast;
pValue = new RTFValue(m_nHRule);
}
break;
case NS_ooxml::LN_CT_FramePr_wrap:
if (m_oWrap)
pValue = new RTFValue(*m_oWrap);
break;
default:
break;
}
if (pValue)
sprms.set(nId, pValue);
}
RTFSprms frameprSprms;
frameprSprms.set(NS_ooxml::LN_CT_PPrBase_framePr, new RTFValue(sprms));
return frameprSprms;
}
bool RTFFrame::hasProperties() const
{
// tdf#153178 \dxfrtext \dfrmtxtx \dfrmtxty \wrapdefault do *not* create frame
return m_nX != 0 || m_nY != 0 || m_nW != 0 || m_nH != 0
|| (m_nHoriAlign && m_nHoriAlign != NS_ooxml::LN_Value_doc_ST_XAlign_left)
|| (m_nHoriAnchor && m_nHoriAnchor != NS_ooxml::LN_Value_doc_ST_HAnchor_text)
|| (m_nVertAlign && m_nVertAlign != NS_ooxml::LN_Value_doc_ST_YAlign_inline)
|| (m_nVertAnchor && m_nVertAnchor != NS_ooxml::LN_Value_doc_ST_VAnchor_margin)
|| (m_oWrap && *m_oWrap != NS_ooxml::LN_Value_doc_ST_Wrap_auto);
}
} // namespace writerfilter
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
↑ V530 The return value of function 'append' is required to be utilized.
↑ V530 The return value of function 'append' is required to be utilized.
↑ V530 The return value of function 'append' is required to be utilized.
↑ V530 The return value of function 'append' is required to be utilized.
↑ V530 The return value of function 'append' is required to be utilized.
↑ V530 The return value of function 'append' is required to be utilized.
↑ V530 The return value of function 'append' is required to be utilized.
↑ V530 The return value of function 'append' is required to be utilized.
↑ V530 The return value of function 'append' is required to be utilized.
↑ V530 The return value of function 'append' is required to be utilized.