summaryrefslogtreecommitdiffstats
path: root/writerfilter/source/rtftok/rtfdocumentimpl.cxx
diff options
context:
space:
mode:
Diffstat (limited to 'writerfilter/source/rtftok/rtfdocumentimpl.cxx')
-rw-r--r--writerfilter/source/rtftok/rtfdocumentimpl.cxx3992
1 files changed, 3992 insertions, 0 deletions
diff --git a/writerfilter/source/rtftok/rtfdocumentimpl.cxx b/writerfilter/source/rtftok/rtfdocumentimpl.cxx
new file mode 100644
index 000000000..4011a17d5
--- /dev/null
+++ b/writerfilter/source/rtftok/rtfdocumentimpl.cxx
@@ -0,0 +1,3992 @@
+/* -*- 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 <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 <tools/diagnose_ex.h>
+#include <tools/globname.hxx>
+#include <tools/datetimeutils.hxx>
+#include <comphelper/classids.hxx>
+#include <comphelper/embeddedobjectcontainer.hxx>
+#include <svl/lngmisc.hxx>
+#include <sfx2/sfxbasemodel.hxx>
+#include <sfx2/classificationhelper.hxx>
+#include <oox/mathml/import.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"
+
+using namespace com::sun::star;
+
+namespace
+{
+/// Returns an util::DateTime from a 'YYYY. MM. DD.' string.
+util::DateTime getDateTimeFromUserProp(const OUString& rString)
+{
+ util::DateTime aRet;
+ sal_Int32 nLen = rString.getLength();
+ if (nLen >= 4)
+ {
+ aRet.Year = o3tl::toInt32(rString.subView(0, 4));
+
+ if (nLen >= 8 && rString.match(". ", 4))
+ {
+ aRet.Month = o3tl::toInt32(rString.subView(6, 2));
+
+ if (nLen >= 12 && rString.match(". ", 8))
+ aRet.Day = o3tl::toInt32(rString.subView(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);
+}
+
+OString DTTM22OString(tools::Long nDTTM)
+{
+ return DateTimeToOString(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;
+}
+
+const char* keywordToString(RTFKeyword nKeyword)
+{
+ for (int i = 0; i < nRTFControlWords; i++)
+ {
+ if (nKeyword == aRTFControlWords[i].GetIndex())
+ return aRTFControlWords[i].GetKeyword();
+ }
+ return nullptr;
+}
+
+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,
+ uno::Reference<lang::XComponent> 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("InsertMode", false))
+ , m_rMediaDescriptor(rMediaDescriptor)
+ , m_hasRHeader(false)
+ , m_hasFHeader(false)
+ , m_hasRFooter(false)
+ , m_hasFFooter(false)
+ , m_bAfterCellBeforeRow(false)
+{
+ OSL_ASSERT(xInputStream.is());
+ m_pInStream = utl::UcbStreamHelper::CreateStream(xInputStream, true);
+
+ m_xModelFactory.set(m_xDstDoc, uno::UNO_QUERY);
+
+ uno::Reference<document::XDocumentPropertiesSupplier> xDocumentPropertiesSupplier(
+ m_xDstDoc, uno::UNO_QUERY);
+ if (xDocumentPropertiesSupplier.is())
+ m_xDocumentProperties = xDocumentPropertiesSupplier->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);
+}
+
+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)
+ 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);
+ }
+ }
+
+ // Get rid of direct formatting what is already in the style.
+ 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
+ bool hasBreakBeforeFrame
+ = m_aStates.top().getFrame().hasProperties()
+ && 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 (m_aStates.top().getFrame().hasProperties())
+ {
+ 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);
+ }
+
+ // 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_uInt8 const sBreak[] = { 0xd };
+ Mapper().text(sBreak, 1);
+ m_bNeedCr = false;
+}
+
+void RTFDocumentImpl::tableBreak()
+{
+ 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
+ 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)
+ 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);
+ dispatchSymbol(RTFKeyword::PAR);
+ m_bNeedSect = bNeedSect;
+ }
+ 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);
+ Mapper().endParagraphGroup();
+
+ // 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;
+ if (m_aStyleNames.find(nIndex) != m_aStyleNames.end())
+ aRet = m_aStyleNames[nIndex];
+ return aRet;
+ }
+
+ return m_pSuperstream->getStyleName(nIndex);
+}
+
+Id RTFDocumentImpl::getStyleType(int nIndex)
+{
+ if (!m_pSuperstream)
+ {
+ Id nRet = 0;
+ if (m_aStyleTypes.find(nIndex) != m_aStyleTypes.end())
+ nRet = m_aStyleTypes[nIndex];
+ 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.
+ OString aStr = OUStringToOString(m_aStates.top().getDestinationText().makeStringAndClear(),
+ RTL_TEXTENCODING_ASCII_US);
+ 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("com.sun.star.text.TextFrame"))
+ 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("com.sun.star.drawing.GraphicObjectShape"))
+ {
+ // 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())
+ {
+ if (m_xModelFactory.is())
+ xShape.set(m_xModelFactory->createInstance("com.sun.star.drawing.GraphicObjectShape"),
+ uno::UNO_QUERY);
+ uno::Reference<drawing::XDrawPageSupplier> const xDrawSupplier(m_xDstDoc, uno::UNO_QUERY);
+ if (xDrawSupplier.is())
+ {
+ uno::Reference<drawing::XShapes> xShapes = xDrawSupplier->getDrawPage();
+ if (xShapes.is())
+ xShapes->add(xShape);
+ }
+ }
+
+ uno::Reference<beans::XPropertySet> xPropertySet(xShape, uno::UNO_QUERY);
+
+ if (xPropertySet.is())
+ xPropertySet->setPropertyValue("Graphic", uno::Any(xGraphic));
+
+ // 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("AnchorType",
+ 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;
+ 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;
+}
+
+bool RTFFrame::inFrame() const { return m_nW > 0 || m_nH > 0 || m_nX > 0 || m_nY > 0; }
+
+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->push_back(Buf_t(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->push_back(Buf_t(BUFFER_TEXT, pValue, nullptr));
+ pCurrentBuffer->push_back(Buf_t(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->push_back(Buf_t(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(reinterpret_cast<sal_uInt8 const*>(rString.getStr()), rString.getLength());
+ else
+ {
+ auto pValue = new RTFValue(rString);
+ pCurrentBuffer->push_back(Buf_t(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->push_back(Buf_t(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>& rCellsSrpms,
+ ::std::deque<RTFSprms>& rCellsAttributes, int const nCells)
+{
+ for (int i = 0; i < nCells; ++i)
+ {
+ replayBuffer(rBuffer, &rCellsSrpms.front(), &rCellsAttributes.front());
+ rCellsSrpms.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(rCellsSrpms.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)
+ {
+ // 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(), 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(reinterpret_cast<sal_uInt8 const*>(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() = 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::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 '" << keywordToString(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:
+ 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);
+ }
+ 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() = 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()->push_back(
+ Buf_t(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()->push_back(
+ Buf_t(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(reinterpret_cast<sal_uInt8 const*>(str.getStr()), str.getLength());
+ singleChar(cFieldSep);
+ // 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)
+ {
+ if (&m_aStates.top().getDestinationText()
+ != m_aStates.top().getCurrentDestinationText())
+ break; // not for nested group
+ OString aStr = OUStringToOString(
+ m_aStates.top().getCurrentDestinationText()->makeStringAndClear(),
+ rState.getCurrentEncoding());
+ // 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())
+ m_xDocumentProperties->setKeywords(comphelper::string::convertCommaSeparated(
+ m_aStates.top().getCurrentDestinationText()->makeStringAndClear()));
+ 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 ? OUString("Operator")
+ : OUString("Company");
+ 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:
+ {
+ if (&m_aStates.top().getDestinationText()
+ != m_aStates.top().getCurrentDestinationText())
+ break; // not for nested group
+ OUString aStr(OStringToOUString(
+ DTTM22OString(
+ m_aStates.top().getCurrentDestinationText()->makeStringAndClear().toInt32()),
+ rState.getCurrentEncoding()));
+ 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("com.sun.star.text.TextFrame");
+
+ // The default is certainly not inline, but then what Word supports is just at-character.
+ xPropertySet->setPropertyValue("AnchorType",
+ uno::Any(text::TextContentAnchorType_AT_CHARACTER));
+
+ if (bTextFrame)
+ {
+ xPropertySet->setPropertyValue("HoriOrientPosition",
+ uno::Any(rDrawing.getLeft()));
+ xPropertySet->setPropertyValue("VertOrientPosition",
+ 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(
+ "FillColor", 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("FillTransparence", 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;
+ if (rState.getFrame().inFrame())
+ {
+ // 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);
+ // gcc4.4 (and 4.3 and possibly older) have a problem with dynamic_cast directly to the target class,
+ // so help it with an intermediate cast. I'm not sure what exactly the problem is, seems to be unrelated
+ // to RTLD_GLOBAL, so most probably a gcc bug.
+ auto& rImport = dynamic_cast<oox::FormulaImportBase&>(
+ dynamic_cast<SfxBaseModel&>(*xComponent));
+ rImport.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.
+ uno::Reference<document::XDocumentPropertiesSupplier> xDocumentPropertiesSupplier(
+ m_xDstDoc, uno::UNO_QUERY);
+ if (xDocumentPropertiesSupplier.is())
+ m_xDocumentProperties = xDocumentPropertiesSupplier->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_xModelFactory->createInstance("com.sun.star.text.FieldMaster.User"),
+ uno::UNO_QUERY_THROW);
+ xMaster->setPropertyValue("Name", uno::Any(m_aStates.top().getDocVarName()));
+ uno::Reference<text::XDependentTextField> xField(
+ m_xModelFactory->createInstance("com.sun.star.text.TextField.User"),
+ uno::UNO_QUERY);
+ xField->attachTextFieldMaster(xMaster);
+ xField->getTextFieldMaster()->setPropertyValue("Content", 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()->push_back(
+ Buf_t(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().inFrame();
+
+ // 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 && m_bIsNewDoc)
+ dispatchSymbol(RTFKeyword::PAR);
+ if (m_bNeedSect) // may be set by dispatchSymbol above!
+ sectBreak(true);
+ }
+
+ 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()
+{
+ OString aStr
+ = OUStringToOString(m_aStates.top().getCurrentDestinationText()->makeStringAndClear(),
+ RTL_TEXTENCODING_ASCII_US);
+ 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));
+ auto pStreamValue = new RTFValue(xInputStream);
+ m_aOLEAttributes.set(NS_ooxml::LN_inputstream, pStreamValue);
+
+ 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 (m_aStates.top().getDestination() == Destination::FONTENTRY
+ && m_aStates.top().getCurrentEncoding() == RTL_TEXTENCODING_SYMBOL)
+ 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)
+{
+ rBuffer.emplace_back(BUFFER_SETSTYLE, new RTFValue(m_aStates.top().getCurrentStyleIndex()),
+ nullptr);
+ rBuffer.emplace_back(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)
+{
+ if (m_pDocumentImpl->getFirstRun() && !m_pDocumentImpl->isStyleSheetImport())
+ {
+ m_pDocumentImpl->checkFirstRun();
+ m_pDocumentImpl->setNeedPar(false);
+ }
+ 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;
+ }
+}
+
+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_margin;
+ 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
+{
+ return 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;
+}
+
+} // namespace writerfilter
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */