diff options
Diffstat (limited to 'sw/source/filter/ww8/rtfattributeoutput.cxx')
-rw-r--r-- | sw/source/filter/ww8/rtfattributeoutput.cxx | 4442 |
1 files changed, 4442 insertions, 0 deletions
diff --git a/sw/source/filter/ww8/rtfattributeoutput.cxx b/sw/source/filter/ww8/rtfattributeoutput.cxx new file mode 100644 index 000000000..6fa088f5f --- /dev/null +++ b/sw/source/filter/ww8/rtfattributeoutput.cxx @@ -0,0 +1,4442 @@ +/* -*- 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/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include "rtfattributeoutput.hxx" +#include <memory> +#include <cstring> +#include "rtfsdrexport.hxx" +#include "writerwordglue.hxx" +#include "ww8par.hxx" +#include <fmtcntnt.hxx> +#include <rtl/tencinfo.h> +#include <sal/log.hxx> +#include <sot/exchange.hxx> +#include <svtools/rtfkeywd.hxx> +#include <editeng/fontitem.hxx> +#include <editeng/tstpitem.hxx> +#include <editeng/adjustitem.hxx> +#include <editeng/spltitem.hxx> +#include <editeng/widwitem.hxx> +#include <editeng/postitem.hxx> +#include <editeng/wghtitem.hxx> +#include <editeng/kernitem.hxx> +#include <editeng/crossedoutitem.hxx> +#include <editeng/cmapitem.hxx> +#include <editeng/wrlmitem.hxx> +#include <editeng/udlnitem.hxx> +#include <editeng/langitem.hxx> +#include <editeng/escapementitem.hxx> +#include <editeng/fhgtitem.hxx> +#include <editeng/colritem.hxx> +#include <editeng/hyphenzoneitem.hxx> +#include <editeng/contouritem.hxx> +#include <editeng/shdditem.hxx> +#include <editeng/autokernitem.hxx> +#include <editeng/emphasismarkitem.hxx> +#include <editeng/twolinesitem.hxx> +#include <editeng/charscaleitem.hxx> +#include <editeng/charrotateitem.hxx> +#include <editeng/charreliefitem.hxx> +#include <editeng/paravertalignitem.hxx> +#include <editeng/blinkitem.hxx> +#include <editeng/charhiddenitem.hxx> +#include <editeng/boxitem.hxx> +#include <editeng/brushitem.hxx> +#include <editeng/ulspitem.hxx> +#include <editeng/shaditem.hxx> +#include <editeng/keepitem.hxx> +#include <editeng/frmdiritem.hxx> +#include <editeng/opaqitem.hxx> +#include <o3tl/unit_conversion.hxx> +#include <svx/svdouno.hxx> +#include <filter/msfilter/rtfutil.hxx> +#include <sfx2/sfxbasemodel.hxx> +#include <svx/xfillit0.hxx> +#include <svx/xflgrit.hxx> +#include <docufld.hxx> +#include <fmtclds.hxx> +#include <fmtrowsplt.hxx> +#include <fmtline.hxx> +#include <fmtanchr.hxx> +#include <ftninfo.hxx> +#include <htmltbl.hxx> +#include <ndgrf.hxx> +#include <pagedesc.hxx> +#include <swmodule.hxx> +#include <txtftn.hxx> +#include <txtinet.hxx> +#include <grfatr.hxx> +#include <ndole.hxx> +#include <lineinfo.hxx> +#include <redline.hxx> +#include <rtf.hxx> +#include <IDocumentSettingAccess.hxx> +#include <vcl/cvtgrf.hxx> +#include <oox/mathml/export.hxx> +#include <com/sun/star/i18n/ScriptType.hpp> +#include <svl/grabbagitem.hxx> +#include <frmatr.hxx> +#include <swtable.hxx> +#include "rtfexport.hxx" + +using namespace ::com::sun::star; +using namespace sw::util; + +static OString OutTBLBorderLine(RtfExport const& rExport, const editeng::SvxBorderLine* pLine, + const char* pStr) +{ + OStringBuffer aRet; + if (pLine && !pLine->isEmpty()) + { + aRet.append(pStr); + // single line + switch (pLine->GetBorderLineStyle()) + { + case SvxBorderLineStyle::SOLID: + { + if (SvxBorderLineWidth::Hairline == pLine->GetWidth()) + aRet.append(OOO_STRING_SVTOOLS_RTF_BRDRHAIR); + else + aRet.append(OOO_STRING_SVTOOLS_RTF_BRDRS); + } + break; + case SvxBorderLineStyle::DOTTED: + aRet.append(OOO_STRING_SVTOOLS_RTF_BRDRDOT); + break; + case SvxBorderLineStyle::DASHED: + aRet.append(OOO_STRING_SVTOOLS_RTF_BRDRDASH); + break; + case SvxBorderLineStyle::DOUBLE: + case SvxBorderLineStyle::DOUBLE_THIN: + aRet.append(OOO_STRING_SVTOOLS_RTF_BRDRDB); + break; + case SvxBorderLineStyle::THINTHICK_SMALLGAP: + aRet.append(OOO_STRING_SVTOOLS_RTF_BRDRTNTHSG); + break; + case SvxBorderLineStyle::THINTHICK_MEDIUMGAP: + aRet.append(OOO_STRING_SVTOOLS_RTF_BRDRTNTHMG); + break; + case SvxBorderLineStyle::THINTHICK_LARGEGAP: + aRet.append(OOO_STRING_SVTOOLS_RTF_BRDRTNTHLG); + break; + case SvxBorderLineStyle::THICKTHIN_SMALLGAP: + aRet.append(OOO_STRING_SVTOOLS_RTF_BRDRTHTNSG); + break; + case SvxBorderLineStyle::THICKTHIN_MEDIUMGAP: + aRet.append(OOO_STRING_SVTOOLS_RTF_BRDRTHTNMG); + break; + case SvxBorderLineStyle::THICKTHIN_LARGEGAP: + aRet.append(OOO_STRING_SVTOOLS_RTF_BRDRTHTNLG); + break; + case SvxBorderLineStyle::EMBOSSED: + aRet.append(OOO_STRING_SVTOOLS_RTF_BRDREMBOSS); + break; + case SvxBorderLineStyle::ENGRAVED: + aRet.append(OOO_STRING_SVTOOLS_RTF_BRDRENGRAVE); + break; + case SvxBorderLineStyle::OUTSET: + aRet.append(OOO_STRING_SVTOOLS_RTF_BRDROUTSET); + break; + case SvxBorderLineStyle::INSET: + aRet.append(OOO_STRING_SVTOOLS_RTF_BRDRINSET); + break; + case SvxBorderLineStyle::FINE_DASHED: + aRet.append(OOO_STRING_SVTOOLS_RTF_BRDRDASHSM); + break; + case SvxBorderLineStyle::DASH_DOT: + aRet.append(OOO_STRING_SVTOOLS_RTF_BRDRDASHD); + break; + case SvxBorderLineStyle::DASH_DOT_DOT: + aRet.append(OOO_STRING_SVTOOLS_RTF_BRDRDASHDD); + break; + case SvxBorderLineStyle::NONE: + default: + aRet.append(OOO_STRING_SVTOOLS_RTF_BRDRNONE); + break; + } + + double const fConverted( + ::editeng::ConvertBorderWidthToWord(pLine->GetBorderLineStyle(), pLine->GetWidth())); + if (255 >= pLine->GetWidth()) // That value comes from RTF specs + { + aRet.append(OOO_STRING_SVTOOLS_RTF_BRDRW + + OString::number(static_cast<sal_Int32>(fConverted))); + } + else + { + // use \brdrth to double the value range... + aRet.append(OOO_STRING_SVTOOLS_RTF_BRDRTH OOO_STRING_SVTOOLS_RTF_BRDRW); + aRet.append(static_cast<sal_Int32>(fConverted) / 2); + } + + aRet.append(OOO_STRING_SVTOOLS_RTF_BRDRCF); + aRet.append(static_cast<sal_Int32>(rExport.GetColor(pLine->GetColor()))); + } + else // tdf#129758 "no border" may be needed to override style + { + aRet.append(pStr); + aRet.append(OOO_STRING_SVTOOLS_RTF_BRDRNONE); + } + return aRet.makeStringAndClear(); +} + +static OString OutBorderLine(RtfExport const& rExport, const editeng::SvxBorderLine* pLine, + const char* pStr, sal_uInt16 nDist, + SvxShadowLocation eShadowLocation = SvxShadowLocation::NONE) +{ + OStringBuffer aRet; + aRet.append(OutTBLBorderLine(rExport, pLine, pStr)); + if (pLine) + { + aRet.append(OOO_STRING_SVTOOLS_RTF_BRSP); + aRet.append(static_cast<sal_Int32>(nDist)); + } + if (eShadowLocation == SvxShadowLocation::BottomRight) + aRet.append(LO_STRING_SVTOOLS_RTF_BRDRSH); + return aRet.makeStringAndClear(); +} + +void RtfAttributeOutput::RTLAndCJKState(bool bIsRTL, sal_uInt16 nScript) +{ + m_bIsRTL = bIsRTL; + m_nScript = nScript; + m_bControlLtrRtl = true; +} + +sal_Int32 RtfAttributeOutput::StartParagraph(ww8::WW8TableNodeInfo::Pointer_t pTextNodeInfo, + bool /*bGenerateParaId*/) +{ + if (m_bIsBeforeFirstParagraph && m_rExport.m_nTextTyp != TXT_HDFT) + m_bIsBeforeFirstParagraph = false; + + // Output table/table row/table cell starts if needed + if (pTextNodeInfo) + { + sal_uInt32 nRow = pTextNodeInfo->getRow(); + sal_uInt32 nCell = pTextNodeInfo->getCell(); + + // New cell/row? + if (m_nTableDepth > 0 && !m_bTableCellOpen) + { + ww8::WW8TableNodeInfoInner::Pointer_t pDeepInner( + pTextNodeInfo->getInnerForDepth(m_nTableDepth)); + OSL_ENSURE(pDeepInner, "TableNodeInfoInner not found"); + // Make sure we always start a row between ending one and starting a cell. + // In case of subtables, we may not get the first cell. + if (pDeepInner && (pDeepInner->getCell() == 0 || m_bTableRowEnded)) + { + StartTableRow(pDeepInner); + } + + StartTableCell(); + } + + // Again, if depth was incremented, start a new table even if we skipped the first cell. + if ((nRow == 0 && nCell == 0) || (m_nTableDepth == 0 && pTextNodeInfo->getDepth())) + { + // Do we have to start the table? + // [If we are at the right depth already, it means that we + // continue the table cell] + sal_uInt32 nCurrentDepth = pTextNodeInfo->getDepth(); + + if (nCurrentDepth > m_nTableDepth) + { + // Start all the tables that begin here + for (sal_uInt32 nDepth = m_nTableDepth + 1; nDepth <= pTextNodeInfo->getDepth(); + ++nDepth) + { + ww8::WW8TableNodeInfoInner::Pointer_t pInner( + pTextNodeInfo->getInnerForDepth(nDepth)); + + m_bLastTable = (nDepth == pTextNodeInfo->getDepth()); + StartTable(); + StartTableRow(pInner); + StartTableCell(); + } + + m_nTableDepth = nCurrentDepth; + } + } + } + + OSL_ENSURE(m_aRun.getLength() == 0, "m_aRun is not empty"); + return 0; +} + +void RtfAttributeOutput::EndParagraph(ww8::WW8TableNodeInfoInner::Pointer_t pTextNodeInfoInner) +{ + bool bLastPara = false; + if (m_rExport.m_nTextTyp == TXT_FTN || m_rExport.m_nTextTyp == TXT_EDN + || m_rExport.m_rDoc.IsClipBoard()) + { + // We're ending a paragraph that is the last paragraph of a footnote or endnote, or of clipboard. + bLastPara + = m_rExport.GetCurrentNodeIndex() + && m_rExport.GetCurrentNodeIndex() == m_rExport.m_pCurPam->End()->nNode.GetIndex(); + } + + FinishTableRowCell(pTextNodeInfoInner); + + RtfStringBuffer aParagraph; + + aParagraph.appendAndClear(m_aRun); + aParagraph->append(m_aAfterRuns.makeStringAndClear()); + if (m_bTableAfterCell) + m_bTableAfterCell = false; + else + { + aParagraph->append(SAL_NEWLINE_STRING); + // RTF_PAR at the end of the footnote or clipboard, would cause an additional empty paragraph. + if (!bLastPara) + { + aParagraph->append(OOO_STRING_SVTOOLS_RTF_PAR); + aParagraph->append(' '); + } + } + if (m_nColBreakNeeded) + { + aParagraph->append(OOO_STRING_SVTOOLS_RTF_COLUMN); + m_nColBreakNeeded = false; + } + + if (!m_bBufferSectionHeaders) + aParagraph.makeStringAndClear(this); + else + m_aSectionHeaders.append(aParagraph.makeStringAndClear()); +} + +void RtfAttributeOutput::EmptyParagraph() +{ + m_rExport.Strm() + .WriteCharPtr(SAL_NEWLINE_STRING) + .WriteCharPtr(OOO_STRING_SVTOOLS_RTF_PAR) + .WriteChar(' '); +} + +void RtfAttributeOutput::SectionBreaks(const SwNode& rNode) +{ + SwNodeIndex aNextIndex(rNode, 1); + if (rNode.IsTextNode()) + { + OSL_ENSURE(m_aStyles.getLength() == 0, "m_aStyles is not empty"); + + // output page/section breaks + m_rExport.Strm().WriteOString(m_aSectionBreaks.makeStringAndClear()); + m_bBufferSectionBreaks = true; + + // output section headers / footers + if (!m_bBufferSectionHeaders) + m_rExport.Strm().WriteOString(m_aSectionHeaders.makeStringAndClear()); + + if (aNextIndex.GetNode().IsTextNode()) + { + const SwTextNode* pTextNode = static_cast<SwTextNode*>(&aNextIndex.GetNode()); + m_rExport.OutputSectionBreaks(pTextNode->GetpSwAttrSet(), *pTextNode); + // Save the current page description for now, so later we will be able to access the previous one. + m_pPrevPageDesc = pTextNode->FindPageDesc(); + } + else if (aNextIndex.GetNode().IsTableNode()) + { + const SwTableNode* pTableNode = static_cast<SwTableNode*>(&aNextIndex.GetNode()); + const SwFrameFormat* pFormat = pTableNode->GetTable().GetFrameFormat(); + m_rExport.OutputSectionBreaks(&(pFormat->GetAttrSet()), *pTableNode); + } + m_bBufferSectionBreaks = false; + } + else if (rNode.IsEndNode()) + { + // End of something: make sure that it's the end of a table. + assert(rNode.StartOfSectionNode()->IsTableNode()); + if (aNextIndex.GetNode().IsTextNode()) + { + // Handle section break between a table and a text node following it. + const SwTextNode* pTextNode = aNextIndex.GetNode().GetTextNode(); + m_rExport.OutputSectionBreaks(pTextNode->GetpSwAttrSet(), *pTextNode); + } + } +} + +void RtfAttributeOutput::StartParagraphProperties() +{ + OStringBuffer aPar; + if (!m_rExport.GetRTFFlySyntax()) + { + aPar.append(OOO_STRING_SVTOOLS_RTF_PARD); + aPar.append(OOO_STRING_SVTOOLS_RTF_PLAIN); + aPar.append(' '); + } + if (!m_bBufferSectionHeaders) + m_rExport.Strm().WriteOString(aPar.makeStringAndClear()); + else + m_aSectionHeaders.append(aPar); +} + +void RtfAttributeOutput::EndParagraphProperties( + const SfxItemSet& /*rParagraphMarkerProperties*/, const SwRedlineData* /*pRedlineData*/, + const SwRedlineData* /*pRedlineParagraphMarkerDeleted*/, + const SwRedlineData* /*pRedlineParagraphMarkerInserted*/) +{ + // Do not call MoveCharacterProperties(), + // Otherwise associate properties in the paragraph style are ruined. + const OString aProperties = m_aStyles.makeStringAndClear(); + m_rExport.Strm().WriteOString(aProperties); +} + +void RtfAttributeOutput::StartRun(const SwRedlineData* pRedlineData, sal_Int32 /*nPos*/, + bool bSingleEmptyRun) +{ + SAL_INFO("sw.rtf", __func__ << ", bSingleEmptyRun: " << bSingleEmptyRun); + + m_bInRun = true; + m_bSingleEmptyRun = bSingleEmptyRun; + if (!m_bSingleEmptyRun) + m_aRun->append('{'); + + // if there is some redlining in the document, output it + Redline(pRedlineData); + + OSL_ENSURE(m_aRunText.getLength() == 0, "m_aRunText is not empty"); +} + +void RtfAttributeOutput::EndRun(const SwTextNode* /*pNode*/, sal_Int32 /*nPos*/, bool /*bLastRun*/) +{ + m_aRun->append(SAL_NEWLINE_STRING); + m_aRun.appendAndClear(m_aRunText); + + if (m_bInRuby) + { + m_aRun->append(")}}{" OOO_STRING_SVTOOLS_RTF_FLDRSLT " {}}}"); + m_bInRuby = false; + } + + if (!m_bSingleEmptyRun && m_bInRun) + m_aRun->append('}'); + m_bInRun = false; +} + +void RtfAttributeOutput::StartRunProperties() +{ + OSL_ENSURE(m_aStyles.getLength() == 0, "m_aStyles is not empty"); +} + +void RtfAttributeOutput::EndRunProperties(const SwRedlineData* /*pRedlineData*/) +{ + const OString aProperties = MoveCharacterProperties(true); + m_aRun->append(aProperties); +} + +OString RtfAttributeOutput::MoveCharacterProperties(bool aAutoWriteRtlLtr) +{ + const OString aAssocHich = m_aStylesAssocHich.makeStringAndClear(); + const OString aAssocDbch = m_aStylesAssocDbch.makeStringAndClear(); + const OString aAssocRtlch = m_aStylesAssocRtlch.makeStringAndClear(); + const OString aAssocLtrch = m_aStylesAssocLtrch.makeStringAndClear(); + const OString aNormal = m_aStyles.makeStringAndClear(); + OStringBuffer aBuf; + + if (aAutoWriteRtlLtr && !m_bControlLtrRtl) + { + m_bControlLtrRtl = !aAssocRtlch.isEmpty(); + m_bIsRTL = false; + m_nScript = i18n::ScriptType::LATIN; + } + + if (m_bIsRTL) + { + if (!aAssocRtlch.isEmpty()) + { + aBuf.append(OOO_STRING_SVTOOLS_RTF_LTRCH + aAssocLtrch + + " " OOO_STRING_SVTOOLS_RTF_RTLCH + aAssocRtlch); + } + } + else + { + if (!aAssocRtlch.isEmpty()) + { + aBuf.append(OOO_STRING_SVTOOLS_RTF_RTLCH + aAssocRtlch + + " " OOO_STRING_SVTOOLS_RTF_LTRCH + aAssocLtrch); + } + if (!aAssocHich.isEmpty()) + { + aBuf.append(OOO_STRING_SVTOOLS_RTF_HICH + aAssocHich); + } + if (!aNormal.isEmpty()) + { + aBuf.append(OOO_STRING_SVTOOLS_RTF_LOCH + aNormal); + } + if (!aAssocDbch.isEmpty()) + { + aBuf.append(OOO_STRING_SVTOOLS_RTF_DBCH + aAssocDbch); + } + } + + if (m_bControlLtrRtl) + { + m_bControlLtrRtl = false; + + switch (m_nScript) + { + case i18n::ScriptType::LATIN: + aBuf.append(OOO_STRING_SVTOOLS_RTF_LOCH); + break; + case i18n::ScriptType::ASIAN: + aBuf.append(OOO_STRING_SVTOOLS_RTF_DBCH); + break; + case i18n::ScriptType::COMPLEX: + /* noop */ + default: + /* should not happen? */ + break; + } + } + + return aBuf.makeStringAndClear(); +} + +void RtfAttributeOutput::RunText(const OUString& rText, rtl_TextEncoding /*eCharSet*/) +{ + SAL_INFO("sw.rtf", __func__ << ", rText: " << rText); + RawText(rText, m_rExport.GetCurrentEncoding()); +} + +OStringBuffer& RtfAttributeOutput::RunText() { return m_aRunText.getLastBuffer(); } + +void RtfAttributeOutput::RawText(const OUString& rText, rtl_TextEncoding eCharSet) +{ + m_aRunText->append(msfilter::rtfutil::OutString(rText, eCharSet)); +} + +void RtfAttributeOutput::StartRuby(const SwTextNode& rNode, sal_Int32 /*nPos*/, + const SwFormatRuby& rRuby) +{ + WW8Ruby aWW8Ruby(rNode, rRuby, GetExport()); + OUString aStr(FieldString(ww::eEQ) + "\\* jc"); + aStr += OUString::number(aWW8Ruby.GetJC()) + " \\* \"Font:" + aWW8Ruby.GetFontFamily() + + "\" \\* hps"; + aStr += OUString::number((aWW8Ruby.GetRubyHeight() + 5) / 10) + " \\o"; + if (aWW8Ruby.GetDirective()) + { + aStr += "\\a" + OUStringChar(aWW8Ruby.GetDirective()); + } + aStr += "(\\s\\up " + OUString::number((aWW8Ruby.GetBaseHeight() + 10) / 20 - 1) + "("; + m_rExport.OutputField(nullptr, ww::eEQ, aStr, FieldFlags::Start | FieldFlags::CmdStart); + aStr = rRuby.GetText() + "),"; + m_rExport.OutputField(nullptr, ww::eEQ, aStr, FieldFlags::NONE); + m_bInRuby = true; +} + +void RtfAttributeOutput::EndRuby(const SwTextNode& /*rNode*/, sal_Int32 /*nPos*/) {} + +bool RtfAttributeOutput::StartURL(const OUString& rUrl, const OUString& rTarget) +{ + m_aURLs.push(rUrl); + // Ignore hyperlink without a URL. + if (!rUrl.isEmpty()) + { + m_aRun->append('{'); + m_aRun->append(OOO_STRING_SVTOOLS_RTF_FIELD); + m_aRun->append('{'); + m_aRun->append(OOO_STRING_SVTOOLS_RTF_IGNORE); + m_aRun->append(OOO_STRING_SVTOOLS_RTF_FLDINST); + m_aRun->append(" HYPERLINK "); + + m_aRun->append("\""); + m_aRun->append(msfilter::rtfutil::OutString(rUrl, m_rExport.GetCurrentEncoding())); + m_aRun->append("\" "); + + if (!rTarget.isEmpty()) + { + m_aRun->append("\\\\t \""); + m_aRun->append(msfilter::rtfutil::OutString(rTarget, m_rExport.GetCurrentEncoding())); + m_aRun->append("\" "); + } + + m_aRun->append("}"); + m_aRun->append("{" OOO_STRING_SVTOOLS_RTF_FLDRSLT " {"); + } + return true; +} + +bool RtfAttributeOutput::EndURL(bool const isAtEndOfParagraph) +{ + if (m_aURLs.empty()) + { + return true; + } + + const OUString& rURL = m_aURLs.top(); + if (!rURL.isEmpty()) + { + // UGLY: usually EndRun is called earlier, but there is an extra + // call to OutAttrWithRange() when at the end of the paragraph, + // so in that special case the output needs to be appended to the + // new run's text instead of the previous run + if (isAtEndOfParagraph) + { + // close the fldrslt group + m_aRunText->append("}}"); + // close the field group + m_aRunText->append('}'); + } + else + { + // close the fldrslt group + m_aRun->append("}}"); + // close the field group + m_aRun->append('}'); + } + } + m_aURLs.pop(); + return true; +} + +void RtfAttributeOutput::FieldVanish(const OUString& /*rText*/, ww::eField /*eType*/, + OUString const* /*pBookmarkName*/) +{ + SAL_INFO("sw.rtf", "TODO: " << __func__); +} + +void RtfAttributeOutput::Redline(const SwRedlineData* pRedline) +{ + if (!pRedline) + return; + + if (pRedline->GetType() == RedlineType::Insert) + { + m_aRun->append(OOO_STRING_SVTOOLS_RTF_REVISED); + m_aRun->append(OOO_STRING_SVTOOLS_RTF_REVAUTH); + m_aRun->append(static_cast<sal_Int32>( + m_rExport.GetRedline(SW_MOD()->GetRedlineAuthor(pRedline->GetAuthor())))); + m_aRun->append(OOO_STRING_SVTOOLS_RTF_REVDTTM); + } + else if (pRedline->GetType() == RedlineType::Delete) + { + m_aRun->append(OOO_STRING_SVTOOLS_RTF_DELETED); + m_aRun->append(OOO_STRING_SVTOOLS_RTF_REVAUTHDEL); + m_aRun->append(static_cast<sal_Int32>( + m_rExport.GetRedline(SW_MOD()->GetRedlineAuthor(pRedline->GetAuthor())))); + m_aRun->append(OOO_STRING_SVTOOLS_RTF_REVDTTMDEL); + } + m_aRun->append(static_cast<sal_Int32>(sw::ms::DateTime2DTTM(pRedline->GetTimeStamp()))); + m_aRun->append(' '); +} + +void RtfAttributeOutput::FormatDrop(const SwTextNode& /*rNode*/, + const SwFormatDrop& /*rSwFormatDrop*/, sal_uInt16 /*nStyle*/, + ww8::WW8TableNodeInfo::Pointer_t /*pTextNodeInfo*/, + ww8::WW8TableNodeInfoInner::Pointer_t /*pTextNodeInfoInner*/) +{ + SAL_INFO("sw.rtf", "TODO: " << __func__); +} + +void RtfAttributeOutput::ParagraphStyle(sal_uInt16 nStyle) +{ + OString* pStyle = m_rExport.GetStyle(nStyle); + OStringBuffer aStyle; + aStyle.append(OOO_STRING_SVTOOLS_RTF_S); + aStyle.append(static_cast<sal_Int32>(nStyle)); + if (pStyle) + aStyle.append(*pStyle); + if (!m_bBufferSectionHeaders) + m_rExport.Strm().WriteOString(aStyle); + else + m_aSectionHeaders.append(aStyle); +} + +void RtfAttributeOutput::TableInfoCell( + ww8::WW8TableNodeInfoInner::Pointer_t /*pTableTextNodeInfoInner*/) +{ + m_aStyles.append(OOO_STRING_SVTOOLS_RTF_INTBL); + if (m_nTableDepth > 1) + { + m_aStyles.append(OOO_STRING_SVTOOLS_RTF_ITAP); + m_aStyles.append(static_cast<sal_Int32>(m_nTableDepth)); + } + m_bWroteCellInfo = true; +} + +void RtfAttributeOutput::TableInfoRow(ww8::WW8TableNodeInfoInner::Pointer_t /*pTableTextNodeInfo*/) +{ + /* noop */ +} + +void RtfAttributeOutput::TableDefinition( + ww8::WW8TableNodeInfoInner::Pointer_t pTableTextNodeInfoInner) +{ + InitTableHelper(pTableTextNodeInfoInner); + + const SwTable* pTable = pTableTextNodeInfoInner->getTable(); + SwFrameFormat* pFormat = pTable->GetFrameFormat(); + + m_aRowDefs.append(OOO_STRING_SVTOOLS_RTF_TROWD); + TableOrientation(pTableTextNodeInfoInner); + TableBidi(pTableTextNodeInfoInner); + TableHeight(pTableTextNodeInfoInner); + TableCanSplit(pTableTextNodeInfoInner); + + // Cell margins + const SvxBoxItem& rBox = pFormat->GetBox(); + static const SvxBoxItemLine aBorders[] = { SvxBoxItemLine::TOP, SvxBoxItemLine::LEFT, + SvxBoxItemLine::BOTTOM, SvxBoxItemLine::RIGHT }; + + static const char* aRowPadNames[] + = { OOO_STRING_SVTOOLS_RTF_TRPADDT, OOO_STRING_SVTOOLS_RTF_TRPADDL, + OOO_STRING_SVTOOLS_RTF_TRPADDB, OOO_STRING_SVTOOLS_RTF_TRPADDR }; + + static const char* aRowPadUnits[] + = { OOO_STRING_SVTOOLS_RTF_TRPADDFT, OOO_STRING_SVTOOLS_RTF_TRPADDFL, + OOO_STRING_SVTOOLS_RTF_TRPADDFB, OOO_STRING_SVTOOLS_RTF_TRPADDFR }; + + for (int i = 0; i < 4; ++i) + { + m_aRowDefs.append(aRowPadUnits[i]); + m_aRowDefs.append(sal_Int32(3)); + m_aRowDefs.append(aRowPadNames[i]); + m_aRowDefs.append(static_cast<sal_Int32>(rBox.GetDistance(aBorders[i]))); + } + + // The cell-dependent properties + const double fWidthRatio = m_pTableWrt->GetAbsWidthRatio(); + const SwWriteTableRows& aRows = m_pTableWrt->GetRows(); + SwWriteTableRow* pRow = aRows[pTableTextNodeInfoInner->getRow()].get(); + SwTwips nSz = 0; + + // Not using m_nTableDepth, which is not yet incremented here. + sal_uInt32 nCurrentDepth = pTableTextNodeInfoInner->getDepth(); + m_aCells[nCurrentDepth] = pRow->GetCells().size(); + for (sal_uInt32 i = 0; i < m_aCells[nCurrentDepth]; i++) + { + const SwWriteTableCell* const pCell = pRow->GetCells()[i].get(); + const SwFrameFormat* pCellFormat = pCell->GetBox()->GetFrameFormat(); + + pTableTextNodeInfoInner->setCell(i); + TableCellProperties(pTableTextNodeInfoInner); + + // Right boundary: this can't be in TableCellProperties as the old + // value of nSz is needed. + nSz += pCellFormat->GetFrameSize().GetWidth(); + m_aRowDefs.append(OOO_STRING_SVTOOLS_RTF_CELLX); + m_aRowDefs.append(static_cast<sal_Int32>(pFormat->GetLRSpace().GetLeft() + + rtl::math::round(nSz * fWidthRatio))); + } +} + +void RtfAttributeOutput::TableDefaultBorders( + ww8::WW8TableNodeInfoInner::Pointer_t pTableTextNodeInfoInner) +{ + /* + * The function name is a bit misleading: given that we write borders + * before each row, we just have borders, not default ones. Additionally, + * this function actually writes borders for a specific cell only and is + * called for each cell. + */ + + const SwWriteTableRows& aRows = m_pTableWrt->GetRows(); + SwWriteTableRow* pRow = aRows[pTableTextNodeInfoInner->getRow()].get(); + const SwWriteTableCell* const pCell + = pRow->GetCells()[pTableTextNodeInfoInner->getCell()].get(); + const SwFrameFormat* pCellFormat = pCell->GetBox()->GetFrameFormat(); + const SvxBoxItem* pItem = pCellFormat->GetAttrSet().GetItemIfSet(RES_BOX); + if (!pItem) + return; + + auto& rBox = *pItem; + static const SvxBoxItemLine aBorders[] = { SvxBoxItemLine::TOP, SvxBoxItemLine::LEFT, + SvxBoxItemLine::BOTTOM, SvxBoxItemLine::RIGHT }; + static const char* aBorderNames[] + = { OOO_STRING_SVTOOLS_RTF_CLBRDRT, OOO_STRING_SVTOOLS_RTF_CLBRDRL, + OOO_STRING_SVTOOLS_RTF_CLBRDRB, OOO_STRING_SVTOOLS_RTF_CLBRDRR }; + //Yes left and top are swapped with each other for cell padding! Because + //that's what the thundering annoying rtf export/import word xp does. + static const char* aCellPadNames[] + = { OOO_STRING_SVTOOLS_RTF_CLPADL, OOO_STRING_SVTOOLS_RTF_CLPADT, + OOO_STRING_SVTOOLS_RTF_CLPADB, OOO_STRING_SVTOOLS_RTF_CLPADR }; + static const char* aCellPadUnits[] + = { OOO_STRING_SVTOOLS_RTF_CLPADFL, OOO_STRING_SVTOOLS_RTF_CLPADFT, + OOO_STRING_SVTOOLS_RTF_CLPADFB, OOO_STRING_SVTOOLS_RTF_CLPADFR }; + for (int i = 0; i < 4; ++i) + { + if (const editeng::SvxBorderLine* pLn = rBox.GetLine(aBorders[i])) + m_aRowDefs.append(OutTBLBorderLine(m_rExport, pLn, aBorderNames[i])); + if (rBox.GetDistance(aBorders[i])) + { + m_aRowDefs.append(aCellPadUnits[i]); + m_aRowDefs.append(sal_Int32(3)); + m_aRowDefs.append(aCellPadNames[i]); + m_aRowDefs.append(static_cast<sal_Int32>(rBox.GetDistance(aBorders[i]))); + } + } +} + +void RtfAttributeOutput::TableBackgrounds( + ww8::WW8TableNodeInfoInner::Pointer_t pTableTextNodeInfoInner) +{ + const SwTable* pTable = pTableTextNodeInfoInner->getTable(); + const SwTableBox* pTableBox = pTableTextNodeInfoInner->getTableBox(); + const SwTableLine* pTableLine = pTableBox->GetUpper(); + + Color aColor = COL_AUTO; + auto pTableColorProp + = pTable->GetFrameFormat()->GetAttrSet().GetItem<SvxBrushItem>(RES_BACKGROUND); + if (pTableColorProp) + aColor = pTableColorProp->GetColor(); + + auto pRowColorProp + = pTableLine->GetFrameFormat()->GetAttrSet().GetItem<SvxBrushItem>(RES_BACKGROUND); + if (pRowColorProp && pRowColorProp->GetColor() != COL_AUTO) + aColor = pRowColorProp->GetColor(); + + const SwWriteTableRows& aRows = m_pTableWrt->GetRows(); + SwWriteTableRow* pRow = aRows[pTableTextNodeInfoInner->getRow()].get(); + const SwWriteTableCell* const pCell + = pRow->GetCells()[pTableTextNodeInfoInner->getCell()].get(); + const SwFrameFormat* pCellFormat = pCell->GetBox()->GetFrameFormat(); + if (const SvxBrushItem* pBrushItem = pCellFormat->GetAttrSet().GetItemIfSet(RES_BACKGROUND)) + { + if (pBrushItem->GetColor() != COL_AUTO) + aColor = pBrushItem->GetColor(); + } + + if (!aColor.IsTransparent()) + { + m_aRowDefs.append(OOO_STRING_SVTOOLS_RTF_CLCBPAT); + m_aRowDefs.append(static_cast<sal_Int32>(m_rExport.GetColor(aColor))); + } +} + +void RtfAttributeOutput::TableRowRedline( + ww8::WW8TableNodeInfoInner::Pointer_t /*pTableTextNodeInfoInner*/) +{ +} + +void RtfAttributeOutput::TableCellRedline( + ww8::WW8TableNodeInfoInner::Pointer_t /*pTableTextNodeInfoInner*/) +{ +} + +void RtfAttributeOutput::TableHeight(ww8::WW8TableNodeInfoInner::Pointer_t pTableTextNodeInfoInner) +{ + const SwTableBox* pTabBox = pTableTextNodeInfoInner->getTableBox(); + const SwTableLine* pTabLine = pTabBox->GetUpper(); + const SwFrameFormat* pLineFormat = pTabLine->GetFrameFormat(); + const SwFormatFrameSize& rLSz = pLineFormat->GetFrameSize(); + + if (!(SwFrameSize::Variable != rLSz.GetHeightSizeType() && rLSz.GetHeight())) + return; + + sal_Int32 nHeight = 0; + + switch (rLSz.GetHeightSizeType()) + { + case SwFrameSize::Fixed: + nHeight = -rLSz.GetHeight(); + break; + case SwFrameSize::Minimum: + nHeight = rLSz.GetHeight(); + break; + default: + break; + } + + if (nHeight) + { + m_aRowDefs.append(OOO_STRING_SVTOOLS_RTF_TRRH); + m_aRowDefs.append(nHeight); + } +} + +void RtfAttributeOutput::TableCanSplit( + ww8::WW8TableNodeInfoInner::Pointer_t pTableTextNodeInfoInner) +{ + const SwTableBox* pTabBox = pTableTextNodeInfoInner->getTableBox(); + const SwTableLine* pTabLine = pTabBox->GetUpper(); + const SwFrameFormat* pLineFormat = pTabLine->GetFrameFormat(); + const SwFormatRowSplit& rSplittable = pLineFormat->GetRowSplit(); + + // The rtf default is to allow a row to break + if (!rSplittable.GetValue()) + m_aRowDefs.append(OOO_STRING_SVTOOLS_RTF_TRKEEP); +} + +void RtfAttributeOutput::TableBidi(ww8::WW8TableNodeInfoInner::Pointer_t pTableTextNodeInfoInner) +{ + const SwTable* pTable = pTableTextNodeInfoInner->getTable(); + const SwFrameFormat* pFrameFormat = pTable->GetFrameFormat(); + + if (m_rExport.TrueFrameDirection(*pFrameFormat) != SvxFrameDirection::Horizontal_RL_TB) + m_aRowDefs.append(OOO_STRING_SVTOOLS_RTF_LTRROW); + else + m_aRowDefs.append(OOO_STRING_SVTOOLS_RTF_RTLROW); +} + +void RtfAttributeOutput::TableVerticalCell( + ww8::WW8TableNodeInfoInner::Pointer_t pTableTextNodeInfoInner) +{ + const SwWriteTableRows& aRows = m_pTableWrt->GetRows(); + SwWriteTableRow* pRow = aRows[pTableTextNodeInfoInner->getRow()].get(); + const SwWriteTableCell* const pCell + = pRow->GetCells()[pTableTextNodeInfoInner->getCell()].get(); + const SwFrameFormat* pCellFormat = pCell->GetBox()->GetFrameFormat(); + + // Text direction. + if (SvxFrameDirection::Vertical_RL_TB == m_rExport.TrueFrameDirection(*pCellFormat)) + m_aRowDefs.append(OOO_STRING_SVTOOLS_RTF_CLTXTBRL); + else if (SvxFrameDirection::Vertical_LR_BT == m_rExport.TrueFrameDirection(*pCellFormat)) + m_aRowDefs.append(OOO_STRING_SVTOOLS_RTF_CLTXBTLR); + + // vertical merges + if (pCell->GetRowSpan() > 1) + m_aRowDefs.append(OOO_STRING_SVTOOLS_RTF_CLVMGF); + else if (pCell->GetRowSpan() == 0) + m_aRowDefs.append(OOO_STRING_SVTOOLS_RTF_CLVMRG); + + // vertical alignment + const SwFormatVertOrient* pVertOrientItem + = pCellFormat->GetAttrSet().GetItemIfSet(RES_VERT_ORIENT); + if (!pVertOrientItem) + return; + + switch (pVertOrientItem->GetVertOrient()) + { + case text::VertOrientation::CENTER: + m_aRowDefs.append(OOO_STRING_SVTOOLS_RTF_CLVERTALC); + break; + case text::VertOrientation::BOTTOM: + m_aRowDefs.append(OOO_STRING_SVTOOLS_RTF_CLVERTALB); + break; + default: + m_aRowDefs.append(OOO_STRING_SVTOOLS_RTF_CLVERTALT); + break; + } +} + +void RtfAttributeOutput::TableNodeInfoInner(ww8::WW8TableNodeInfoInner::Pointer_t pNodeInfoInner) +{ + // This is called when the nested table ends in a cell, and there's no + // paragraph behind that; so we must check for the ends of cell, rows, + // and tables + FinishTableRowCell(pNodeInfoInner); +} + +void RtfAttributeOutput::TableOrientation( + ww8::WW8TableNodeInfoInner::Pointer_t pTableTextNodeInfoInner) +{ + const SwTable* pTable = pTableTextNodeInfoInner->getTable(); + SwFrameFormat* pFormat = pTable->GetFrameFormat(); + + OStringBuffer aTableAdjust(OOO_STRING_SVTOOLS_RTF_TRQL); + switch (pFormat->GetHoriOrient().GetHoriOrient()) + { + case text::HoriOrientation::CENTER: + aTableAdjust.setLength(0); + aTableAdjust.append(OOO_STRING_SVTOOLS_RTF_TRQC); + break; + case text::HoriOrientation::RIGHT: + aTableAdjust.setLength(0); + aTableAdjust.append(OOO_STRING_SVTOOLS_RTF_TRQR); + break; + case text::HoriOrientation::NONE: + case text::HoriOrientation::LEFT_AND_WIDTH: + aTableAdjust.append(OOO_STRING_SVTOOLS_RTF_TRLEFT); + aTableAdjust.append(static_cast<sal_Int32>(pFormat->GetLRSpace().GetLeft())); + break; + default: + break; + } + + m_aRowDefs.append(aTableAdjust); +} + +void RtfAttributeOutput::TableSpacing( + ww8::WW8TableNodeInfoInner::Pointer_t /*pTableTextNodeInfoInner*/) +{ + SAL_INFO("sw.rtf", "TODO: " << __func__); +} + +void RtfAttributeOutput::TableRowEnd(sal_uInt32 /*nDepth*/) { /* noop, see EndTableRow() */} + +/* + * Our private table methods. + */ + +void RtfAttributeOutput::InitTableHelper( + const ww8::WW8TableNodeInfoInner::Pointer_t& pTableTextNodeInfoInner) +{ + const SwTable* pTable = pTableTextNodeInfoInner->getTable(); + if (m_pTableWrt && pTable == m_pTableWrt->GetTable()) + return; + + tools::Long nPageSize = 0; + bool bRelBoxSize = false; + + // Create the SwWriteTable instance to use col spans + GetTablePageSize(pTableTextNodeInfoInner.get(), nPageSize, bRelBoxSize); + + const SwFrameFormat* pFormat = pTable->GetFrameFormat(); + const sal_uInt32 nTableSz = pFormat->GetFrameSize().GetWidth(); + + const SwHTMLTableLayout* pLayout = pTable->GetHTMLTableLayout(); + if (pLayout && pLayout->IsExportable()) + m_pTableWrt = std::make_unique<SwWriteTable>(pTable, pLayout); + else + m_pTableWrt = std::make_unique<SwWriteTable>(pTable, pTable->GetTabLines(), nPageSize, + nTableSz, false); +} + +void RtfAttributeOutput::StartTable() +{ + // To trigger calling InitTableHelper() + m_pTableWrt.reset(); +} + +void RtfAttributeOutput::StartTableRow( + const ww8::WW8TableNodeInfoInner::Pointer_t& pTableTextNodeInfoInner) +{ + sal_uInt32 nCurrentDepth = pTableTextNodeInfoInner->getDepth(); + SAL_INFO("sw.rtf", __func__ << ", (depth is " << nCurrentDepth << ")"); + m_bTableRowEnded = false; + + TableDefinition(pTableTextNodeInfoInner); + + if (!m_bLastTable) + m_aTables.push_back(m_aRowDefs.makeStringAndClear()); + + // We'll write the table definition for nested tables later + if (nCurrentDepth > 1) + return; + // Empty the previous row closing buffer before starting the new one, + // necessary for subtables. + m_rExport.Strm().WriteOString(m_aAfterRuns.makeStringAndClear()); + m_rExport.Strm().WriteOString(m_aRowDefs.makeStringAndClear()); +} + +void RtfAttributeOutput::StartTableCell() { m_bTableCellOpen = true; } + +void RtfAttributeOutput::TableCellProperties( + const ww8::WW8TableNodeInfoInner::Pointer_t& pTableTextNodeInfoInner) +{ + TableDefaultBorders(pTableTextNodeInfoInner); + TableBackgrounds(pTableTextNodeInfoInner); + TableVerticalCell(pTableTextNodeInfoInner); +} + +void RtfAttributeOutput::EndTableCell() +{ + SAL_INFO("sw.rtf", __func__ << ", (depth is " << m_nTableDepth << ")"); + + if (!m_bWroteCellInfo) + { + m_aAfterRuns.append(OOO_STRING_SVTOOLS_RTF_INTBL); + m_aAfterRuns.append(OOO_STRING_SVTOOLS_RTF_ITAP); + m_aAfterRuns.append(static_cast<sal_Int32>(m_nTableDepth)); + } + if (m_nTableDepth > 1) + m_aAfterRuns.append(OOO_STRING_SVTOOLS_RTF_NESTCELL); + else + m_aAfterRuns.append(OOO_STRING_SVTOOLS_RTF_CELL); + + m_bTableCellOpen = false; + m_bTableAfterCell = true; + m_bWroteCellInfo = false; + if (m_aCells[m_nTableDepth] > 0) + m_aCells[m_nTableDepth]--; +} + +void RtfAttributeOutput::EndTableRow() +{ + SAL_INFO("sw.rtf", __func__ << ", (depth is " << m_nTableDepth << ")"); + + // Trying to end the row without writing the required number of cells? Fill with empty ones. + for (sal_uInt32 i = 0; i < m_aCells[m_nTableDepth]; i++) + m_aAfterRuns.append(OOO_STRING_SVTOOLS_RTF_CELL); + + if (m_nTableDepth > 1) + { + m_aAfterRuns.append( + "{" OOO_STRING_SVTOOLS_RTF_IGNORE OOO_STRING_SVTOOLS_RTF_NESTTABLEPROPRS); + if (!m_aRowDefs.isEmpty()) + m_aAfterRuns.append(m_aRowDefs.makeStringAndClear()); + else if (!m_aTables.empty()) + { + m_aAfterRuns.append(m_aTables.back()); + m_aTables.pop_back(); + } + m_aAfterRuns.append(OOO_STRING_SVTOOLS_RTF_NESTROW + "}" + "{" OOO_STRING_SVTOOLS_RTF_NONESTTABLES OOO_STRING_SVTOOLS_RTF_PAR "}"); + } + else + { + if (!m_aTables.empty()) + { + m_aAfterRuns.append(m_aTables.back()); + m_aTables.pop_back(); + } + m_aAfterRuns.append(OOO_STRING_SVTOOLS_RTF_ROW OOO_STRING_SVTOOLS_RTF_PARD); + } + m_bTableRowEnded = true; +} + +void RtfAttributeOutput::EndTable() +{ + if (m_nTableDepth > 0) + { + m_nTableDepth--; + m_pTableWrt.reset(); + } + + // We closed the table; if it is a nested table, the cell that contains it + // still continues + m_bTableCellOpen = true; + + // Cleans the table helper + m_pTableWrt.reset(); +} + +void RtfAttributeOutput::FinishTableRowCell(const ww8::WW8TableNodeInfoInner::Pointer_t& pInner) +{ + if (!pInner) + return; + + // Where are we in the table + sal_uInt32 nRow = pInner->getRow(); + + const SwTable* pTable = pInner->getTable(); + const SwTableLines& rLines = pTable->GetTabLines(); + sal_uInt16 nLinesCount = rLines.size(); + + if (pInner->isEndOfCell()) + EndTableCell(); + + // This is a line end + if (pInner->isEndOfLine()) + EndTableRow(); + + // This is the end of the table + if (pInner->isEndOfLine() && (nRow + 1) == nLinesCount) + EndTable(); +} + +void RtfAttributeOutput::StartStyles() +{ + m_rExport.Strm() + .WriteCharPtr(SAL_NEWLINE_STRING) + .WriteChar('{') + .WriteCharPtr(OOO_STRING_SVTOOLS_RTF_COLORTBL); + m_rExport.OutColorTable(); + OSL_ENSURE(m_aStylesheet.getLength() == 0, "m_aStylesheet is not empty"); + m_aStylesheet.append(SAL_NEWLINE_STRING); + m_aStylesheet.append('{'); + m_aStylesheet.append(OOO_STRING_SVTOOLS_RTF_STYLESHEET); +} + +void RtfAttributeOutput::EndStyles(sal_uInt16 /*nNumberOfStyles*/) +{ + m_rExport.Strm().WriteChar('}'); + m_rExport.Strm().WriteOString(m_aStylesheet.makeStringAndClear()); + m_rExport.Strm().WriteChar('}'); +} + +void RtfAttributeOutput::DefaultStyle() { /* noop, the default style is always 0 in RTF */} + +void RtfAttributeOutput::StartStyle(const OUString& rName, StyleType eType, sal_uInt16 nBase, + sal_uInt16 nNext, sal_uInt16 /*nLink*/, sal_uInt16 /*nWwId*/, + sal_uInt16 nId, bool bAutoUpdate) +{ + SAL_INFO("sw.rtf", __func__ << ", rName = '" << rName << "'"); + + m_aStylesheet.append('{'); + if (eType == STYLE_TYPE_PARA) + m_aStylesheet.append(OOO_STRING_SVTOOLS_RTF_S); + else + m_aStylesheet.append(OOO_STRING_SVTOOLS_RTF_IGNORE OOO_STRING_SVTOOLS_RTF_CS); + m_aStylesheet.append(static_cast<sal_Int32>(nId)); + + if (nBase != 0x0FFF) + { + m_aStylesheet.append(OOO_STRING_SVTOOLS_RTF_SBASEDON); + m_aStylesheet.append(static_cast<sal_Int32>(nBase)); + } + + m_aStylesheet.append(OOO_STRING_SVTOOLS_RTF_SNEXT); + m_aStylesheet.append(static_cast<sal_Int32>(nNext)); + + if (bAutoUpdate) + m_aStylesheet.append(OOO_STRING_SVTOOLS_RTF_SAUTOUPD); + + m_rStyleName = rName; + m_nStyleId = nId; +} + +void RtfAttributeOutput::EndStyle() +{ + OString aStyles = MoveCharacterProperties(); + m_rExport.InsStyle(m_nStyleId, aStyles); + m_aStylesheet.append(aStyles); + m_aStylesheet.append(' '); + m_aStylesheet.append( + msfilter::rtfutil::OutString(m_rStyleName, m_rExport.GetCurrentEncoding())); + m_aStylesheet.append(";}"); + m_aStylesheet.append(SAL_NEWLINE_STRING); +} + +void RtfAttributeOutput::StartStyleProperties(bool /*bParProp*/, sal_uInt16 /*nStyle*/) +{ + /* noop */ +} + +void RtfAttributeOutput::EndStyleProperties(bool /*bParProp*/) { /* noop */} + +void RtfAttributeOutput::OutlineNumbering(sal_uInt8 nLvl) +{ + if (nLvl >= WW8ListManager::nMaxLevel) + nLvl = WW8ListManager::nMaxLevel - 1; + + m_aStyles.append(OOO_STRING_SVTOOLS_RTF_ILVL); + m_aStyles.append(static_cast<sal_Int32>(nLvl)); + m_aStyles.append(OOO_STRING_SVTOOLS_RTF_OUTLINELEVEL); + m_aStyles.append(static_cast<sal_Int32>(nLvl)); +} + +void RtfAttributeOutput::PageBreakBefore(bool bBreak) +{ + if (bBreak) + { + m_rExport.Strm().WriteCharPtr(OOO_STRING_SVTOOLS_RTF_PAGEBB); + } +} + +void RtfAttributeOutput::SectionBreak(sal_uInt8 nC, bool /*bBreakAfter*/, + const WW8_SepInfo* pSectionInfo, bool /*bExtraPageBreak*/) +{ + switch (nC) + { + case msword::ColumnBreak: + m_nColBreakNeeded = true; + break; + case msword::PageBreak: + if (pSectionInfo) + m_rExport.SectionProperties(*pSectionInfo); + break; + } +} + +void RtfAttributeOutput::StartSection() +{ + if (m_bIsBeforeFirstParagraph) + return; + + m_aSectionBreaks.append(OOO_STRING_SVTOOLS_RTF_SECT OOO_STRING_SVTOOLS_RTF_SECTD); + if (!m_bBufferSectionBreaks) + m_rExport.Strm().WriteOString(m_aSectionBreaks.makeStringAndClear()); +} + +void RtfAttributeOutput::EndSection() +{ + /* + * noop, \sect must go to StartSection or Word won't notice multiple + * columns... + */ +} + +void RtfAttributeOutput::SectionFormProtection(bool bProtected) +{ + m_aSectionBreaks.append(OOO_STRING_SVTOOLS_RTF_SECTUNLOCKED); + m_aSectionBreaks.append(static_cast<sal_Int32>(!bProtected)); +} + +void RtfAttributeOutput::SectionLineNumbering(sal_uLong nRestartNo, + const SwLineNumberInfo& rLnNumInfo) +{ + m_rExport.Strm().WriteCharPtr(OOO_STRING_SVTOOLS_RTF_LINEMOD); + m_rExport.OutLong(rLnNumInfo.GetCountBy()); + m_rExport.Strm().WriteCharPtr(OOO_STRING_SVTOOLS_RTF_LINEX); + m_rExport.OutLong(rLnNumInfo.GetPosFromLeft()); + if (!rLnNumInfo.IsRestartEachPage()) + m_rExport.Strm().WriteCharPtr(OOO_STRING_SVTOOLS_RTF_LINECONT); + + if (nRestartNo > 0) + { + m_rExport.Strm().WriteCharPtr(OOO_STRING_SVTOOLS_RTF_LINESTARTS); + m_rExport.OutLong(nRestartNo); + } +} + +void RtfAttributeOutput::SectionTitlePage() +{ + /* + * noop, handled in RtfExport::WriteHeaderFooter() + */ +} + +void RtfAttributeOutput::SectionPageBorders(const SwFrameFormat* pFormat, + const SwFrameFormat* /*pFirstPageFormat*/) +{ + const SvxBoxItem& rBox = pFormat->GetBox(); + editeng::WordBorderDistances aDistances; + editeng::BorderDistancesToWord(rBox, m_aPageMargins, aDistances); + + if (aDistances.bFromEdge) + { + sal_uInt16 nOpt = (1 << 5); + m_aSectionBreaks.append(OOO_STRING_SVTOOLS_RTF_PGBRDROPT); + m_aSectionBreaks.append(static_cast<sal_Int32>(nOpt)); + } + + const editeng::SvxBorderLine* pLine = rBox.GetTop(); + if (pLine) + m_aSectionBreaks.append( + OutBorderLine(m_rExport, pLine, OOO_STRING_SVTOOLS_RTF_PGBRDRT, aDistances.nTop)); + pLine = rBox.GetBottom(); + if (pLine) + m_aSectionBreaks.append( + OutBorderLine(m_rExport, pLine, OOO_STRING_SVTOOLS_RTF_PGBRDRB, aDistances.nBottom)); + pLine = rBox.GetLeft(); + if (pLine) + m_aSectionBreaks.append( + OutBorderLine(m_rExport, pLine, OOO_STRING_SVTOOLS_RTF_PGBRDRL, aDistances.nLeft)); + pLine = rBox.GetRight(); + if (pLine) + m_aSectionBreaks.append( + OutBorderLine(m_rExport, pLine, OOO_STRING_SVTOOLS_RTF_PGBRDRR, aDistances.nRight)); +} + +void RtfAttributeOutput::SectionBiDi(bool bBiDi) +{ + m_rExport.Strm().WriteCharPtr(bBiDi ? OOO_STRING_SVTOOLS_RTF_RTLSECT + : OOO_STRING_SVTOOLS_RTF_LTRSECT); +} + +void RtfAttributeOutput::SectionPageNumbering(sal_uInt16 nNumType, + const ::std::optional<sal_uInt16>& oPageRestartNumber) +{ + if (oPageRestartNumber) + { + m_aSectionBreaks.append(OOO_STRING_SVTOOLS_RTF_PGNSTARTS); + m_aSectionBreaks.append(static_cast<sal_Int32>(*oPageRestartNumber)); + m_aSectionBreaks.append(OOO_STRING_SVTOOLS_RTF_PGNRESTART); + } + + const char* pStr = nullptr; + switch (nNumType) + { + case SVX_NUM_CHARS_UPPER_LETTER: + case SVX_NUM_CHARS_UPPER_LETTER_N: + pStr = OOO_STRING_SVTOOLS_RTF_PGNUCLTR; + break; + case SVX_NUM_CHARS_LOWER_LETTER: + case SVX_NUM_CHARS_LOWER_LETTER_N: + pStr = OOO_STRING_SVTOOLS_RTF_PGNLCLTR; + break; + case SVX_NUM_ROMAN_UPPER: + pStr = OOO_STRING_SVTOOLS_RTF_PGNUCRM; + break; + case SVX_NUM_ROMAN_LOWER: + pStr = OOO_STRING_SVTOOLS_RTF_PGNLCRM; + break; + + case SVX_NUM_ARABIC: + pStr = OOO_STRING_SVTOOLS_RTF_PGNDEC; + break; + } + if (pStr) + m_aSectionBreaks.append(pStr); +} + +void RtfAttributeOutput::SectionType(sal_uInt8 nBreakCode) +{ + SAL_INFO("sw.rtf", __func__ << ", nBreakCode = " << int(nBreakCode)); + + /* + * break code: 0 No break, 1 New column + * 2 New page, 3 Even page, 4 Odd page + */ + const char* sType = nullptr; + switch (nBreakCode) + { + case 1: + sType = OOO_STRING_SVTOOLS_RTF_SBKCOL; + break; + case 2: + sType = OOO_STRING_SVTOOLS_RTF_SBKPAGE; + break; + case 3: + sType = OOO_STRING_SVTOOLS_RTF_SBKEVEN; + break; + case 4: + sType = OOO_STRING_SVTOOLS_RTF_SBKODD; + break; + default: + sType = OOO_STRING_SVTOOLS_RTF_SBKNONE; + break; + } + m_aSectionBreaks.append(sType); + if (!m_bBufferSectionBreaks) + m_rExport.Strm().WriteOString(m_aSectionBreaks.makeStringAndClear()); +} + +void RtfAttributeOutput::SectFootnoteEndnotePr() +{ + WriteFootnoteEndnotePr(true, m_rExport.m_rDoc.GetFootnoteInfo()); + WriteFootnoteEndnotePr(false, m_rExport.m_rDoc.GetEndNoteInfo()); +} + +void RtfAttributeOutput::WriteFootnoteEndnotePr(bool bFootnote, const SwEndNoteInfo& rInfo) +{ + const char* pOut = nullptr; + + if (bFootnote) + { + switch (rInfo.m_aFormat.GetNumberingType()) + { + default: + pOut = OOO_STRING_SVTOOLS_RTF_SFTNNAR; + break; + case SVX_NUM_CHARS_LOWER_LETTER: + case SVX_NUM_CHARS_LOWER_LETTER_N: + pOut = OOO_STRING_SVTOOLS_RTF_SFTNNALC; + break; + case SVX_NUM_CHARS_UPPER_LETTER: + case SVX_NUM_CHARS_UPPER_LETTER_N: + pOut = OOO_STRING_SVTOOLS_RTF_SFTNNAUC; + break; + case SVX_NUM_ROMAN_LOWER: + pOut = OOO_STRING_SVTOOLS_RTF_SFTNNRLC; + break; + case SVX_NUM_ROMAN_UPPER: + pOut = OOO_STRING_SVTOOLS_RTF_SFTNNRUC; + break; + case SVX_NUM_SYMBOL_CHICAGO: + pOut = OOO_STRING_SVTOOLS_RTF_SFTNNCHI; + break; + } + } + else + { + switch (rInfo.m_aFormat.GetNumberingType()) + { + default: + pOut = OOO_STRING_SVTOOLS_RTF_SAFTNNAR; + break; + case SVX_NUM_CHARS_LOWER_LETTER: + case SVX_NUM_CHARS_LOWER_LETTER_N: + pOut = OOO_STRING_SVTOOLS_RTF_SAFTNNALC; + break; + case SVX_NUM_CHARS_UPPER_LETTER: + case SVX_NUM_CHARS_UPPER_LETTER_N: + pOut = OOO_STRING_SVTOOLS_RTF_SAFTNNAUC; + break; + case SVX_NUM_ROMAN_LOWER: + pOut = OOO_STRING_SVTOOLS_RTF_SAFTNNRLC; + break; + case SVX_NUM_ROMAN_UPPER: + pOut = OOO_STRING_SVTOOLS_RTF_SAFTNNRUC; + break; + case SVX_NUM_SYMBOL_CHICAGO: + pOut = OOO_STRING_SVTOOLS_RTF_SAFTNNCHI; + break; + } + } + + m_aSectionBreaks.append(pOut); + + if (!m_bBufferSectionBreaks) + { + m_rExport.Strm().WriteOString(m_aSectionBreaks.makeStringAndClear()); + } +} + +void RtfAttributeOutput::NumberingDefinition(sal_uInt16 nId, const SwNumRule& /*rRule*/) +{ + m_rExport.Strm().WriteChar('{').WriteCharPtr(OOO_STRING_SVTOOLS_RTF_LISTOVERRIDE); + m_rExport.Strm().WriteCharPtr(OOO_STRING_SVTOOLS_RTF_LISTID); + m_rExport.OutULong(nId); + m_rExport.Strm().WriteCharPtr(OOO_STRING_SVTOOLS_RTF_LISTOVERRIDECOUNT).WriteChar('0'); + m_rExport.Strm().WriteCharPtr(OOO_STRING_SVTOOLS_RTF_LS); + m_rExport.OutULong(nId).WriteChar('}'); +} + +void RtfAttributeOutput::StartAbstractNumbering(sal_uInt16 nId) +{ + m_rExport.Strm() + .WriteChar('{') + .WriteCharPtr(OOO_STRING_SVTOOLS_RTF_LIST) + .WriteCharPtr(OOO_STRING_SVTOOLS_RTF_LISTTEMPLATEID); + m_rExport.OutULong(nId); + m_nListId = nId; +} + +void RtfAttributeOutput::EndAbstractNumbering() +{ + m_rExport.Strm().WriteCharPtr(OOO_STRING_SVTOOLS_RTF_LISTID); + m_rExport.OutULong(m_nListId).WriteChar('}').WriteCharPtr(SAL_NEWLINE_STRING); +} + +void RtfAttributeOutput::NumberingLevel(sal_uInt8 nLevel, sal_uInt16 nStart, + sal_uInt16 nNumberingType, SvxAdjust eAdjust, + const sal_uInt8* pNumLvlPos, sal_uInt8 nFollow, + const wwFont* pFont, const SfxItemSet* pOutSet, + sal_Int16 nIndentAt, sal_Int16 nFirstLineIndex, + sal_Int16 /*nListTabPos*/, const OUString& rNumberingString, + const SvxBrushItem* pBrush) +{ + m_rExport.Strm().WriteCharPtr(SAL_NEWLINE_STRING); + if (nLevel > 8) // RTF knows only 9 levels + m_rExport.Strm() + .WriteCharPtr(OOO_STRING_SVTOOLS_RTF_IGNORE) + .WriteCharPtr(OOO_STRING_SVTOOLS_RTF_SOUTLVL); + + m_rExport.Strm().WriteChar('{').WriteCharPtr(OOO_STRING_SVTOOLS_RTF_LISTLEVEL); + + sal_uInt16 nVal = 0; + switch (nNumberingType) + { + case SVX_NUM_ROMAN_UPPER: + nVal = 1; + break; + case SVX_NUM_ROMAN_LOWER: + nVal = 2; + break; + case SVX_NUM_CHARS_UPPER_LETTER: + case SVX_NUM_CHARS_UPPER_LETTER_N: + nVal = 3; + break; + case SVX_NUM_CHARS_LOWER_LETTER: + case SVX_NUM_CHARS_LOWER_LETTER_N: + nVal = 4; + break; + case SVX_NUM_FULL_WIDTH_ARABIC: + nVal = 14; + break; + case SVX_NUM_CIRCLE_NUMBER: + nVal = 18; + break; + case SVX_NUM_NUMBER_LOWER_ZH: + nVal = 35; + if (pOutSet) + { + const SvxLanguageItem& rLang = pOutSet->Get(RES_CHRATR_CJK_LANGUAGE); + if (rLang.GetLanguage() == LANGUAGE_CHINESE_SIMPLIFIED) + { + nVal = 39; + } + } + break; + case SVX_NUM_NUMBER_UPPER_ZH: + nVal = 38; + break; + case SVX_NUM_NUMBER_UPPER_ZH_TW: + nVal = 34; + break; + case SVX_NUM_TIAN_GAN_ZH: + nVal = 30; + break; + case SVX_NUM_DI_ZI_ZH: + nVal = 31; + break; + case SVX_NUM_NUMBER_TRADITIONAL_JA: + nVal = 16; + break; + case SVX_NUM_AIU_FULLWIDTH_JA: + nVal = 20; + break; + case SVX_NUM_AIU_HALFWIDTH_JA: + nVal = 12; + break; + case SVX_NUM_IROHA_FULLWIDTH_JA: + nVal = 21; + break; + case SVX_NUM_IROHA_HALFWIDTH_JA: + nVal = 13; + break; + case style::NumberingType::HANGUL_SYLLABLE_KO: + nVal = 24; + break; // ganada + case style::NumberingType::HANGUL_JAMO_KO: + nVal = 25; + break; // chosung + case style::NumberingType::HANGUL_CIRCLED_SYLLABLE_KO: + nVal = 24; + break; + case style::NumberingType::HANGUL_CIRCLED_JAMO_KO: + nVal = 25; + break; + case style::NumberingType::NUMBER_HANGUL_KO: + nVal = 42; + break; // koreanCounting + case style::NumberingType::NUMBER_DIGITAL_KO: + nVal = 41; // koreanDigital + break; + case style::NumberingType::NUMBER_DIGITAL2_KO: + nVal = 44; // koreanDigital2 + break; + case style::NumberingType::NUMBER_LEGAL_KO: + nVal = 43; // koreanLegal + break; + + case SVX_NUM_BITMAP: + case SVX_NUM_CHAR_SPECIAL: + nVal = 23; + break; + case SVX_NUM_NUMBER_NONE: + nVal = 255; + break; + case SVX_NUM_ARABIC_ZERO: + nVal = 22; + break; + } + m_rExport.Strm().WriteCharPtr(OOO_STRING_SVTOOLS_RTF_LEVELNFC); + m_rExport.OutULong(nVal); + + switch (eAdjust) + { + case SvxAdjust::Center: + nVal = 1; + break; + case SvxAdjust::Right: + nVal = 2; + break; + default: + nVal = 0; + break; + } + m_rExport.Strm().WriteCharPtr(OOO_STRING_SVTOOLS_RTF_LEVELJC); + m_rExport.OutULong(nVal); + + // bullet + if (nNumberingType == SVX_NUM_BITMAP && pBrush) + { + int nIndex = m_rExport.GetGrfIndex(*pBrush); + if (nIndex != -1) + { + m_rExport.Strm().WriteCharPtr(LO_STRING_SVTOOLS_RTF_LEVELPICTURE); + m_rExport.OutULong(nIndex); + } + } + + m_rExport.Strm().WriteCharPtr(OOO_STRING_SVTOOLS_RTF_LEVELSTARTAT); + m_rExport.OutULong(nStart); + + m_rExport.Strm().WriteCharPtr(OOO_STRING_SVTOOLS_RTF_LEVELFOLLOW); + m_rExport.OutULong(nFollow); + + // leveltext group + m_rExport.Strm().WriteChar('{').WriteCharPtr(OOO_STRING_SVTOOLS_RTF_LEVELTEXT).WriteChar(' '); + + if (SVX_NUM_CHAR_SPECIAL == nNumberingType || SVX_NUM_BITMAP == nNumberingType) + { + m_rExport.Strm().WriteCharPtr("\\'01"); + sal_Unicode cChar = rNumberingString[0]; + m_rExport.Strm().WriteCharPtr("\\u"); + m_rExport.OutULong(cChar); + m_rExport.Strm().WriteCharPtr(" ?"); + } + else + { + m_rExport.Strm().WriteCharPtr("\\'").WriteCharPtr( + msfilter::rtfutil::OutHex(rNumberingString.getLength(), 2).getStr()); + m_rExport.Strm().WriteCharPtr(msfilter::rtfutil::OutString(rNumberingString, + m_rExport.GetDefaultEncoding(), + /*bUnicode =*/false) + .getStr()); + } + + m_rExport.Strm().WriteCharPtr(";}"); + + // write the levelnumbers + m_rExport.Strm().WriteCharPtr("{").WriteCharPtr(OOO_STRING_SVTOOLS_RTF_LEVELNUMBERS); + for (sal_uInt8 i = 0; i <= nLevel && pNumLvlPos[i]; ++i) + { + m_rExport.Strm().WriteCharPtr("\\'").WriteCharPtr( + msfilter::rtfutil::OutHex(pNumLvlPos[i], 2).getStr()); + } + m_rExport.Strm().WriteCharPtr(";}"); + + if (pOutSet) + { + if (pFont) + { + m_rExport.Strm().WriteCharPtr(OOO_STRING_SVTOOLS_RTF_F); + m_rExport.OutULong(m_rExport.m_aFontHelper.GetId(*pFont)); + } + m_rExport.OutputItemSet(*pOutSet, false, true, i18n::ScriptType::LATIN, + m_rExport.m_bExportModeRTF); + const OString aProperties = MoveCharacterProperties(true); + m_rExport.Strm().WriteOString(aProperties); + } + + m_rExport.Strm().WriteCharPtr(OOO_STRING_SVTOOLS_RTF_FI); + m_rExport.OutLong(nFirstLineIndex).WriteCharPtr(OOO_STRING_SVTOOLS_RTF_LI); + m_rExport.OutLong(nIndentAt); + + m_rExport.Strm().WriteChar('}'); + if (nLevel > 8) + m_rExport.Strm().WriteChar('}'); +} + +void RtfAttributeOutput::WriteField_Impl(const SwField* const pField, ww::eField /*eType*/, + const OUString& rFieldCmd, FieldFlags nMode) +{ + // If there are no field instructions, don't export it as a field. + bool bHasInstructions = !rFieldCmd.isEmpty(); + if (FieldFlags::All == nMode) + { + if (bHasInstructions) + { + m_aRunText->append("{" OOO_STRING_SVTOOLS_RTF_FIELD); + if (pField && (pField->GetSubType() & FIXEDFLD)) + m_aRunText->append(OOO_STRING_SVTOOLS_RTF_FLDLOCK); + m_aRunText->append("{" OOO_STRING_SVTOOLS_RTF_IGNORE OOO_STRING_SVTOOLS_RTF_FLDINST + " "); + m_aRunText->append( + msfilter::rtfutil::OutString(rFieldCmd, m_rExport.GetCurrentEncoding())); + m_aRunText->append("}{" OOO_STRING_SVTOOLS_RTF_FLDRSLT " "); + } + if (pField) + m_aRunText->append(msfilter::rtfutil::OutString(pField->ExpandField(true, nullptr), + m_rExport.GetDefaultEncoding())); + if (bHasInstructions) + m_aRunText->append("}}"); + } + else + { + if (nMode & FieldFlags::CmdStart) + { + m_aRunText->append("{" OOO_STRING_SVTOOLS_RTF_FIELD); + m_aRunText->append("{" OOO_STRING_SVTOOLS_RTF_IGNORE OOO_STRING_SVTOOLS_RTF_FLDINST + // paragraph break closes group so open another one "inside" to + " {"); // prevent leaving the field instruction + } + if (bHasInstructions) + m_aRunText->append( + msfilter::rtfutil::OutString(rFieldCmd, m_rExport.GetCurrentEncoding())); + if (nMode & FieldFlags::CmdEnd) + { + m_aRunText->append("}}{" OOO_STRING_SVTOOLS_RTF_FLDRSLT " {"); + } + if (nMode & FieldFlags::Close) + { + m_aRunText->append("}}}"); + } + } +} + +void RtfAttributeOutput::WriteBookmarks_Impl(std::vector<OUString>& rStarts, + std::vector<OUString>& rEnds) +{ + for (const auto& rStart : rStarts) + { + m_aRunText->append("{" OOO_STRING_SVTOOLS_RTF_IGNORE OOO_STRING_SVTOOLS_RTF_BKMKSTART " "); + m_aRunText->append(msfilter::rtfutil::OutString(rStart, m_rExport.GetCurrentEncoding())); + m_aRunText->append('}'); + } + rStarts.clear(); + + for (const auto& rEnd : rEnds) + { + m_aRunText->append("{" OOO_STRING_SVTOOLS_RTF_IGNORE OOO_STRING_SVTOOLS_RTF_BKMKEND " "); + m_aRunText->append(msfilter::rtfutil::OutString(rEnd, m_rExport.GetCurrentEncoding())); + m_aRunText->append('}'); + } + rEnds.clear(); +} + +void RtfAttributeOutput::WriteAnnotationMarks_Impl(std::vector<OUString>& rStarts, + std::vector<OUString>& rEnds) +{ + for (const auto& rStart : rStarts) + { + OString rName = OUStringToOString(rStart, RTL_TEXTENCODING_UTF8); + + // Output the annotation mark + const sal_Int32 nId = m_nNextAnnotationMarkId++; + m_rOpenedAnnotationMarksIds[rName] = nId; + m_aRun->append("{" OOO_STRING_SVTOOLS_RTF_IGNORE OOO_STRING_SVTOOLS_RTF_ATRFSTART " "); + m_aRun->append(nId); + m_aRun->append('}'); + } + rStarts.clear(); + + for (const auto& rEnd : rEnds) + { + OString rName = OUStringToOString(rEnd, RTL_TEXTENCODING_UTF8); + + // Get the id of the annotation mark + auto it = m_rOpenedAnnotationMarksIds.find(rName); + if (it != m_rOpenedAnnotationMarksIds.end()) + { + const sal_Int32 nId = it->second; + m_aRun->append("{" OOO_STRING_SVTOOLS_RTF_IGNORE OOO_STRING_SVTOOLS_RTF_ATRFEND " "); + m_aRun->append(nId); + m_aRun->append('}'); + m_rOpenedAnnotationMarksIds.erase(rName); + + if (m_aPostitFields.find(nId) != m_aPostitFields.end()) + { + m_aRunText->append("{"); + m_nCurrentAnnotationMarkId = nId; + PostitField(m_aPostitFields[nId]); + m_nCurrentAnnotationMarkId = -1; + m_aRunText->append("}"); + } + } + } + rEnds.clear(); +} + +void RtfAttributeOutput::WriteHeaderFooter_Impl(const SwFrameFormat& rFormat, bool bHeader, + const char* pStr, bool bTitlepg) +{ + OStringBuffer aSectionBreaks = m_aSectionBreaks; + m_aSectionBreaks.setLength(0); + RtfStringBuffer aRun = m_aRun; + m_aRun.clear(); + + m_aSectionHeaders.append(bHeader ? OOO_STRING_SVTOOLS_RTF_HEADERY + : OOO_STRING_SVTOOLS_RTF_FOOTERY); + m_aSectionHeaders.append( + static_cast<sal_Int32>(m_rExport.m_pCurrentPageDesc->GetMaster().GetULSpace().GetUpper())); + if (bTitlepg) + m_aSectionHeaders.append(OOO_STRING_SVTOOLS_RTF_TITLEPG); + m_aSectionHeaders.append('{'); + m_aSectionHeaders.append(pStr); + m_bBufferSectionHeaders = true; + m_rExport.WriteHeaderFooterText(rFormat, bHeader); + m_bBufferSectionHeaders = false; + m_aSectionHeaders.append('}'); + + m_aSectionBreaks = aSectionBreaks; + m_aRun = aRun; +} + +namespace +{ +void lcl_TextFrameShadow(std::vector<std::pair<OString, OString>>& rFlyProperties, + const SwFrameFormat& rFrameFormat) +{ + const SvxShadowItem& aShadowItem = rFrameFormat.GetShadow(); + if (aShadowItem.GetLocation() == SvxShadowLocation::NONE) + return; + + rFlyProperties.push_back(std::make_pair<OString, OString>("fShadow", OString::number(1))); + + const Color& rColor = aShadowItem.GetColor(); + // We in fact need RGB to BGR, but the transformation is symmetric. + rFlyProperties.push_back(std::make_pair<OString, OString>( + "shadowColor", OString::number(wwUtility::RGBToBGR(rColor)))); + + // Twips -> points -> EMUs -- hacky, the intermediate step hides rounding errors on roundtrip. + OString aShadowWidth = OString::number(sal_Int32(aShadowItem.GetWidth() / 20) * 12700); + OString aOffsetX; + OString aOffsetY; + switch (aShadowItem.GetLocation()) + { + case SvxShadowLocation::TopLeft: + aOffsetX = "-" + aShadowWidth; + aOffsetY = "-" + aShadowWidth; + break; + case SvxShadowLocation::TopRight: + aOffsetX = aShadowWidth; + aOffsetY = "-" + aShadowWidth; + break; + case SvxShadowLocation::BottomLeft: + aOffsetX = "-" + aShadowWidth; + aOffsetY = aShadowWidth; + break; + case SvxShadowLocation::BottomRight: + aOffsetX = aShadowWidth; + aOffsetY = aShadowWidth; + break; + case SvxShadowLocation::NONE: + case SvxShadowLocation::End: + break; + } + if (!aOffsetX.isEmpty()) + rFlyProperties.emplace_back("shadowOffsetX", aOffsetX); + if (!aOffsetY.isEmpty()) + rFlyProperties.emplace_back("shadowOffsetY", aOffsetY); +} + +void lcl_TextFrameRelativeSize(std::vector<std::pair<OString, OString>>& rFlyProperties, + const SwFrameFormat& rFrameFormat) +{ + const SwFormatFrameSize& rSize = rFrameFormat.GetFrameSize(); + + // Relative size of the Text Frame. + const sal_uInt8 nWidthPercent = rSize.GetWidthPercent(); + if (nWidthPercent && nWidthPercent != SwFormatFrameSize::SYNCED) + { + rFlyProperties.push_back( + std::make_pair<OString, OString>("pctHoriz", OString::number(nWidthPercent * 10))); + + OString aRelation; + switch (rSize.GetWidthPercentRelation()) + { + case text::RelOrientation::PAGE_FRAME: + aRelation = "1"; // page + break; + default: + aRelation = "0"; // margin + break; + } + rFlyProperties.emplace_back(std::make_pair("sizerelh", aRelation)); + } + const sal_uInt8 nHeightPercent = rSize.GetHeightPercent(); + if (!(nHeightPercent && nHeightPercent != SwFormatFrameSize::SYNCED)) + return; + + rFlyProperties.push_back( + std::make_pair<OString, OString>("pctVert", OString::number(nHeightPercent * 10))); + + OString aRelation; + switch (rSize.GetHeightPercentRelation()) + { + case text::RelOrientation::PAGE_FRAME: + aRelation = "1"; // page + break; + default: + aRelation = "0"; // margin + break; + } + rFlyProperties.emplace_back(std::make_pair("sizerelv", aRelation)); +} +} + +void RtfAttributeOutput::writeTextFrame(const ww8::Frame& rFrame, bool bTextBox) +{ + RtfStringBuffer aRunText; + if (bTextBox) + { + m_rExport.setStream(); + aRunText = m_aRunText; + m_aRunText.clear(); + } + + m_rExport.Strm().WriteCharPtr("{" OOO_STRING_SVTOOLS_RTF_SHPTXT); + + { + // Save table state, in case the inner text also contains a table. + ww8::WW8TableInfo::Pointer_t pTableInfoOrig = m_rExport.m_pTableInfo; + m_rExport.m_pTableInfo = std::make_shared<ww8::WW8TableInfo>(); + std::unique_ptr<SwWriteTable> pTableWrt(std::move(m_pTableWrt)); + sal_uInt32 nTableDepth = m_nTableDepth; + + m_nTableDepth = 0; + /* + * Save m_aRun as we should not lose the opening brace. + * OTOH, just drop the contents of m_aRunText in case something + * would be there, causing a problem later. + */ + OString aSave = m_aRun.makeStringAndClear(); + // Also back m_bInRun and m_bSingleEmptyRun up. + bool bInRunOrig = m_bInRun; + m_bInRun = false; + bool bSingleEmptyRunOrig = m_bSingleEmptyRun; + m_bSingleEmptyRun = false; + m_rExport.SetRTFFlySyntax(true); + + const SwFrameFormat& rFrameFormat = rFrame.GetFrameFormat(); + const SwNodeIndex* pNodeIndex = rFrameFormat.GetContent().GetContentIdx(); + SwNodeOffset nStt = pNodeIndex ? pNodeIndex->GetIndex() + 1 : SwNodeOffset(0); + SwNodeOffset nEnd + = pNodeIndex ? pNodeIndex->GetNode().EndOfSectionIndex() : SwNodeOffset(0); + m_rExport.SaveData(nStt, nEnd); + m_rExport.m_pParentFrame = &rFrame; + m_rExport.WriteText(); + m_rExport.RestoreData(); + + m_rExport.Strm().WriteCharPtr(OOO_STRING_SVTOOLS_RTF_PARD); + m_rExport.SetRTFFlySyntax(false); + m_aRun->append(aSave); + m_aRunText.clear(); + m_bInRun = bInRunOrig; + m_bSingleEmptyRun = bSingleEmptyRunOrig; + + // Restore table state. + m_rExport.m_pTableInfo = pTableInfoOrig; + m_pTableWrt = std::move(pTableWrt); + m_nTableDepth = nTableDepth; + } + + m_rExport.m_pParentFrame = nullptr; + + m_rExport.Strm().WriteChar('}'); // shptxt + + if (bTextBox) + { + m_aRunText = aRunText; + m_aRunText->append(m_rExport.getStream()); + m_rExport.resetStream(); + } +} + +/** save the current run state around exporting things that contain paragraphs + themselves like text frames. + TODO: probably more things need to be saved? + */ +class SaveRunState +{ +private: + RtfAttributeOutput& m_rRtf; + RtfStringBuffer m_Run; + RtfStringBuffer m_RunText; + bool const m_bSingleEmptyRun; + bool const m_bInRun; + +public: + explicit SaveRunState(RtfAttributeOutput& rRtf) + : m_rRtf(rRtf) + , m_Run(std::move(rRtf.m_aRun)) + , m_RunText(std::move(rRtf.m_aRunText)) + , m_bSingleEmptyRun(rRtf.m_bSingleEmptyRun) + , m_bInRun(rRtf.m_bInRun) + { + m_rRtf.m_rExport.setStream(); + } + ~SaveRunState() + { + m_rRtf.m_aRun = std::move(m_Run); + m_rRtf.m_aRunText = std::move(m_RunText); + m_rRtf.m_bSingleEmptyRun = m_bSingleEmptyRun; + m_rRtf.m_bInRun = m_bInRun; + + m_rRtf.m_aRunText->append(m_rRtf.m_rExport.getStream()); + m_rRtf.m_rExport.resetStream(); + } +}; + +void RtfAttributeOutput::OutputFlyFrame_Impl(const ww8::Frame& rFrame, const Point& /*rNdTopLeft*/) +{ + const SwNode* pNode = rFrame.GetContent(); + const SwGrfNode* pGrfNode = pNode ? pNode->GetGrfNode() : nullptr; + + switch (rFrame.GetWriterType()) + { + case ww8::Frame::eTextBox: + { + // If this is a TextBox of a shape, then ignore: it's handled in RtfSdrExport::StartShape(). + if (RtfSdrExport::isTextBox(rFrame.GetFrameFormat())) + break; + + SaveRunState const saved(*this); + + m_rExport.m_pParentFrame = &rFrame; + + m_rExport.Strm().WriteCharPtr("{" OOO_STRING_SVTOOLS_RTF_SHP); + m_rExport.Strm().WriteCharPtr( + "{" OOO_STRING_SVTOOLS_RTF_IGNORE OOO_STRING_SVTOOLS_RTF_SHPINST); + + // Shape properties. + m_aFlyProperties.push_back(std::make_pair<OString, OString>( + "shapeType", OString::number(ESCHER_ShpInst_TextBox))); + + // When a frame has some low height, but automatically expanded due + // to lots of contents, this size contains the real size. + const Size aSize = rFrame.GetSize(); + m_pFlyFrameSize = &aSize; + + m_rExport.m_bOutFlyFrameAttrs = true; + m_rExport.SetRTFFlySyntax(true); + m_rExport.OutputFormat(rFrame.GetFrameFormat(), false, false, true); + + // Write ZOrder. + if (const SdrObject* pObject = rFrame.GetFrameFormat().FindRealSdrObject()) + { + m_rExport.Strm().WriteCharPtr(OOO_STRING_SVTOOLS_RTF_SHPZ); + m_rExport.OutULong(pObject->GetOrdNum()); + } + + m_rExport.Strm().WriteOString(m_aRunText.makeStringAndClear()); + m_rExport.Strm().WriteOString(m_aStyles.makeStringAndClear()); + m_rExport.m_bOutFlyFrameAttrs = false; + m_rExport.SetRTFFlySyntax(false); + m_pFlyFrameSize = nullptr; + + const SwFrameFormat& rFrameFormat = rFrame.GetFrameFormat(); + lcl_TextFrameShadow(m_aFlyProperties, rFrameFormat); + lcl_TextFrameRelativeSize(m_aFlyProperties, rFrameFormat); + + for (const std::pair<OString, OString>& rPair : m_aFlyProperties) + { + m_rExport.Strm().WriteCharPtr("{" OOO_STRING_SVTOOLS_RTF_SP "{"); + m_rExport.Strm().WriteCharPtr(OOO_STRING_SVTOOLS_RTF_SN " "); + m_rExport.Strm().WriteOString(rPair.first); + m_rExport.Strm().WriteCharPtr("}{" OOO_STRING_SVTOOLS_RTF_SV " "); + m_rExport.Strm().WriteOString(rPair.second); + m_rExport.Strm().WriteCharPtr("}}"); + } + m_aFlyProperties.clear(); + + writeTextFrame(rFrame); + + m_rExport.Strm().WriteChar('}'); // shpinst + m_rExport.Strm().WriteChar('}'); // shp + + m_rExport.Strm().WriteCharPtr(SAL_NEWLINE_STRING); + } + break; + case ww8::Frame::eGraphic: + if (pGrfNode) + { + m_aRunText.append(dynamic_cast<const SwFlyFrameFormat*>(&rFrame.GetFrameFormat()), + pGrfNode); + } + else if (!rFrame.IsInline()) + { + m_rExport.m_pParentFrame = &rFrame; + m_rExport.SetRTFFlySyntax(true); + m_rExport.OutputFormat(rFrame.GetFrameFormat(), false, false, true); + m_rExport.SetRTFFlySyntax(false); + m_aRunText->append("{" OOO_STRING_SVTOOLS_RTF_IGNORE); + m_rExport.OutputFormat(rFrame.GetFrameFormat(), false, false, true); + m_aRunText->append('}'); + m_rExport.m_pParentFrame = nullptr; + } + break; + case ww8::Frame::eDrawing: + { + const SdrObject* pSdrObj = rFrame.GetFrameFormat().FindRealSdrObject(); + if (pSdrObj) + { + m_aRunText->append("{" OOO_STRING_SVTOOLS_RTF_FIELD "{"); + m_aRunText->append(OOO_STRING_SVTOOLS_RTF_IGNORE); + m_aRunText->append(OOO_STRING_SVTOOLS_RTF_FLDINST); + m_aRunText->append(" SHAPE "); + m_aRunText->append("}" + "{" OOO_STRING_SVTOOLS_RTF_FLDRSLT); + + m_rExport.SdrExporter().AddSdrObject(*pSdrObj); + + m_aRunText->append('}'); + m_aRunText->append('}'); + } + } + break; + case ww8::Frame::eFormControl: + { + const SwFrameFormat& rFrameFormat = rFrame.GetFrameFormat(); + const SdrObject* pObject = rFrameFormat.FindRealSdrObject(); + + m_aRun->append("{" OOO_STRING_SVTOOLS_RTF_FIELD); + m_aRun->append("{" OOO_STRING_SVTOOLS_RTF_IGNORE OOO_STRING_SVTOOLS_RTF_FLDINST); + + if (pObject && pObject->GetObjInventor() == SdrInventor::FmForm) + { + if (auto pFormObj = dynamic_cast<const SdrUnoObj*>(pObject)) + { + const uno::Reference<awt::XControlModel>& xControlModel + = pFormObj->GetUnoControlModel(); + uno::Reference<lang::XServiceInfo> xInfo(xControlModel, uno::UNO_QUERY); + if (xInfo.is()) + { + uno::Reference<beans::XPropertySet> xPropSet(xControlModel, uno::UNO_QUERY); + uno::Reference<beans::XPropertySetInfo> xPropSetInfo + = xPropSet->getPropertySetInfo(); + OUString sName; + if (xInfo->supportsService("com.sun.star.form.component.CheckBox")) + { + m_aRun->append(OUStringToOString(FieldString(ww::eFORMCHECKBOX), + m_rExport.GetCurrentEncoding())); + m_aRun->append( + "{" OOO_STRING_SVTOOLS_RTF_IGNORE OOO_STRING_SVTOOLS_RTF_FORMFIELD + "{"); + m_aRun->append(OOO_STRING_SVTOOLS_RTF_FFTYPE "1"); // 1 = checkbox + // checkbox size in half points, this seems to be always 20 + m_aRun->append(OOO_STRING_SVTOOLS_RTF_FFHPS "20"); + + OUString aStr; + sName = "Name"; + if (xPropSetInfo->hasPropertyByName(sName)) + { + xPropSet->getPropertyValue(sName) >>= aStr; + m_aRun->append( + "{" OOO_STRING_SVTOOLS_RTF_IGNORE OOO_STRING_SVTOOLS_RTF_FFNAME + " "); + m_aRun->append( + OUStringToOString(aStr, m_rExport.GetCurrentEncoding())); + m_aRun->append('}'); + } + + sName = "HelpText"; + if (xPropSetInfo->hasPropertyByName(sName)) + { + xPropSet->getPropertyValue(sName) >>= aStr; + m_aRun->append(OOO_STRING_SVTOOLS_RTF_FFOWNHELP); + m_aRun->append("{" OOO_STRING_SVTOOLS_RTF_IGNORE + OOO_STRING_SVTOOLS_RTF_FFHELPTEXT " "); + m_aRun->append( + OUStringToOString(aStr, m_rExport.GetCurrentEncoding())); + m_aRun->append('}'); + } + + sName = "HelpF1Text"; + if (xPropSetInfo->hasPropertyByName(sName)) + { + xPropSet->getPropertyValue(sName) >>= aStr; + m_aRun->append(OOO_STRING_SVTOOLS_RTF_FFOWNSTAT); + m_aRun->append("{" OOO_STRING_SVTOOLS_RTF_IGNORE + OOO_STRING_SVTOOLS_RTF_FFSTATTEXT " "); + m_aRun->append( + OUStringToOString(aStr, m_rExport.GetCurrentEncoding())); + m_aRun->append('}'); + } + + sal_Int16 nTemp = 0; + xPropSet->getPropertyValue("DefaultState") >>= nTemp; + m_aRun->append(OOO_STRING_SVTOOLS_RTF_FFDEFRES); + m_aRun->append(static_cast<sal_Int32>(nTemp)); + xPropSet->getPropertyValue("State") >>= nTemp; + m_aRun->append(OOO_STRING_SVTOOLS_RTF_FFRES); + m_aRun->append(static_cast<sal_Int32>(nTemp)); + + m_aRun->append("}}"); + + // field result is empty, ffres already contains the form result + m_aRun->append("}{" OOO_STRING_SVTOOLS_RTF_FLDRSLT " "); + } + else if (xInfo->supportsService("com.sun.star.form.component.TextField")) + { + OStringBuffer aBuf; + OString aStr; + OUString aTmp; + const char* pStr; + + m_aRun->append(OUStringToOString(FieldString(ww::eFORMTEXT), + m_rExport.GetCurrentEncoding())); + m_aRun->append( + "{" OOO_STRING_SVTOOLS_RTF_IGNORE OOO_STRING_SVTOOLS_RTF_DATAFIELD + " "); + for (int i = 0; i < 8; i++) + aBuf.append(char(0x00)); + xPropSet->getPropertyValue("Name") >>= aTmp; + aStr = OUStringToOString(aTmp, m_rExport.GetCurrentEncoding()); + aBuf.append(static_cast<char>(aStr.getLength())); + aBuf.append(aStr); + aBuf.append(char(0x00)); + xPropSet->getPropertyValue("DefaultText") >>= aTmp; + aStr = OUStringToOString(aTmp, m_rExport.GetCurrentEncoding()); + aBuf.append(static_cast<char>(aStr.getLength())); + aBuf.append(aStr); + for (int i = 0; i < 11; i++) + aBuf.append(char(0x00)); + aStr = aBuf.makeStringAndClear(); + pStr = aStr.getStr(); + for (int i = 0; i < aStr.getLength(); i++, pStr++) + m_aRun->append(msfilter::rtfutil::OutHex(*pStr, 2)); + m_aRun->append('}'); + m_aRun->append("}{" OOO_STRING_SVTOOLS_RTF_FLDRSLT " "); + xPropSet->getPropertyValue("Text") >>= aTmp; + m_aRun->append(OUStringToOString(aTmp, m_rExport.GetCurrentEncoding())); + m_aRun->append('}'); + m_aRun->append( + "{" OOO_STRING_SVTOOLS_RTF_IGNORE OOO_STRING_SVTOOLS_RTF_FORMFIELD + "{"); + sName = "HelpText"; + if (xPropSetInfo->hasPropertyByName(sName)) + { + xPropSet->getPropertyValue(sName) >>= aTmp; + m_aRun->append(OOO_STRING_SVTOOLS_RTF_FFOWNHELP); + m_aRun->append("{" OOO_STRING_SVTOOLS_RTF_IGNORE + OOO_STRING_SVTOOLS_RTF_FFHELPTEXT " "); + m_aRun->append( + OUStringToOString(aTmp, m_rExport.GetCurrentEncoding())); + m_aRun->append('}'); + } + + sName = "HelpF1Text"; + if (xPropSetInfo->hasPropertyByName(sName)) + { + xPropSet->getPropertyValue(sName) >>= aTmp; + m_aRun->append(OOO_STRING_SVTOOLS_RTF_FFOWNSTAT); + m_aRun->append("{" OOO_STRING_SVTOOLS_RTF_IGNORE + OOO_STRING_SVTOOLS_RTF_FFSTATTEXT " "); + m_aRun->append( + OUStringToOString(aTmp, m_rExport.GetCurrentEncoding())); + m_aRun->append('}'); + } + m_aRun->append("}"); + } + else if (xInfo->supportsService("com.sun.star.form.component.ListBox")) + { + OUString aStr; + uno::Sequence<sal_Int16> aIntSeq; + uno::Sequence<OUString> aStrSeq; + + m_aRun->append(OUStringToOString(FieldString(ww::eFORMDROPDOWN), + m_rExport.GetCurrentEncoding())); + m_aRun->append( + "{" OOO_STRING_SVTOOLS_RTF_IGNORE OOO_STRING_SVTOOLS_RTF_FORMFIELD + "{"); + m_aRun->append(OOO_STRING_SVTOOLS_RTF_FFTYPE "2"); // 2 = list + m_aRun->append(OOO_STRING_SVTOOLS_RTF_FFHASLISTBOX); + + xPropSet->getPropertyValue("DefaultSelection") >>= aIntSeq; + if (aIntSeq.hasElements()) + { + m_aRun->append(OOO_STRING_SVTOOLS_RTF_FFDEFRES); + // a dropdown list can have only one 'selected item by default' + m_aRun->append(static_cast<sal_Int32>(aIntSeq[0])); + } + + xPropSet->getPropertyValue("SelectedItems") >>= aIntSeq; + if (aIntSeq.hasElements()) + { + m_aRun->append(OOO_STRING_SVTOOLS_RTF_FFRES); + // a dropdown list can have only one 'currently selected item' + m_aRun->append(static_cast<sal_Int32>(aIntSeq[0])); + } + + sName = "Name"; + if (xPropSetInfo->hasPropertyByName(sName)) + { + xPropSet->getPropertyValue(sName) >>= aStr; + m_aRun->append( + "{" OOO_STRING_SVTOOLS_RTF_IGNORE OOO_STRING_SVTOOLS_RTF_FFNAME + " "); + m_aRun->append( + OUStringToOString(aStr, m_rExport.GetCurrentEncoding())); + m_aRun->append('}'); + } + + sName = "HelpText"; + if (xPropSetInfo->hasPropertyByName(sName)) + { + xPropSet->getPropertyValue(sName) >>= aStr; + m_aRun->append(OOO_STRING_SVTOOLS_RTF_FFOWNHELP); + m_aRun->append("{" OOO_STRING_SVTOOLS_RTF_IGNORE + OOO_STRING_SVTOOLS_RTF_FFHELPTEXT " "); + m_aRun->append( + OUStringToOString(aStr, m_rExport.GetCurrentEncoding())); + m_aRun->append('}'); + } + + sName = "HelpF1Text"; + if (xPropSetInfo->hasPropertyByName(sName)) + { + xPropSet->getPropertyValue(sName) >>= aStr; + m_aRun->append(OOO_STRING_SVTOOLS_RTF_FFOWNSTAT); + m_aRun->append("{" OOO_STRING_SVTOOLS_RTF_IGNORE + OOO_STRING_SVTOOLS_RTF_FFSTATTEXT " "); + m_aRun->append( + OUStringToOString(aStr, m_rExport.GetCurrentEncoding())); + m_aRun->append('}'); + } + + xPropSet->getPropertyValue("StringItemList") >>= aStrSeq; + for (const auto& rStr : std::as_const(aStrSeq)) + m_aRun->append( + "{" OOO_STRING_SVTOOLS_RTF_IGNORE OOO_STRING_SVTOOLS_RTF_FFL " " + + OUStringToOString(rStr, m_rExport.GetCurrentEncoding()) + + "}"); + + m_aRun->append("}}"); + + // field result is empty, ffres already contains the form result + m_aRun->append("}{" OOO_STRING_SVTOOLS_RTF_FLDRSLT " "); + } + else + SAL_INFO("sw.rtf", __func__ << " unhandled form control: '" + << xInfo->getImplementationName() << "'"); + m_aRun->append('}'); + } + } + } + + m_aRun->append('}'); + } + break; + case ww8::Frame::eOle: + { + const SwFrameFormat& rFrameFormat = rFrame.GetFrameFormat(); + const SdrObject* pSdrObj = rFrameFormat.FindRealSdrObject(); + if (pSdrObj) + { + SwNodeIndex aIdx(*rFrameFormat.GetContent().GetContentIdx(), 1); + SwOLENode& rOLENd = *aIdx.GetNode().GetOLENode(); + FlyFrameOLE(dynamic_cast<const SwFlyFrameFormat*>(&rFrameFormat), rOLENd, + rFrame.GetLayoutSize()); + } + } + break; + default: + SAL_INFO("sw.rtf", __func__ << ": unknown type (" + << static_cast<int>(rFrame.GetWriterType()) << ")"); + break; + } +} + +void RtfAttributeOutput::CharCaseMap(const SvxCaseMapItem& rCaseMap) +{ + switch (rCaseMap.GetValue()) + { + case SvxCaseMap::SmallCaps: + m_aStyles.append(OOO_STRING_SVTOOLS_RTF_SCAPS); + break; + case SvxCaseMap::Uppercase: + m_aStyles.append(OOO_STRING_SVTOOLS_RTF_CAPS); + break; + default: // Something that rtf does not support + m_aStyles.append(OOO_STRING_SVTOOLS_RTF_SCAPS); + m_aStyles.append(sal_Int32(0)); + m_aStyles.append(OOO_STRING_SVTOOLS_RTF_CAPS); + m_aStyles.append(sal_Int32(0)); + break; + } +} + +void RtfAttributeOutput::CharColor(const SvxColorItem& rColor) +{ + const Color aColor(rColor.GetValue()); + + m_aStyles.append(OOO_STRING_SVTOOLS_RTF_CF); + m_aStyles.append(static_cast<sal_Int32>(m_rExport.GetColor(aColor))); +} + +void RtfAttributeOutput::CharContour(const SvxContourItem& rContour) +{ + m_aStyles.append(OOO_STRING_SVTOOLS_RTF_OUTL); + if (!rContour.GetValue()) + m_aStyles.append(sal_Int32(0)); +} + +void RtfAttributeOutput::CharCrossedOut(const SvxCrossedOutItem& rCrossedOut) +{ + switch (rCrossedOut.GetStrikeout()) + { + case STRIKEOUT_NONE: + m_aStyles.append(OOO_STRING_SVTOOLS_RTF_STRIKE); + m_aStyles.append(sal_Int32(0)); + break; + case STRIKEOUT_DOUBLE: + m_aStyles.append(OOO_STRING_SVTOOLS_RTF_STRIKED); + m_aStyles.append(sal_Int32(1)); + break; + default: + m_aStyles.append(OOO_STRING_SVTOOLS_RTF_STRIKE); + break; + } +} + +void RtfAttributeOutput::CharEscapement(const SvxEscapementItem& rEscapement) +{ + short nEsc = rEscapement.GetEsc(); + short nProp = rEscapement.GetProportionalHeight(); + sal_Int32 nProp100 = nProp * 100; + if (DFLT_ESC_PROP == nProp || nProp < 1 || nProp > 100) + { + if (DFLT_ESC_SUB == nEsc || DFLT_ESC_AUTO_SUB == nEsc) + m_aStyles.append(OOO_STRING_SVTOOLS_RTF_SUB); + else if (DFLT_ESC_SUPER == nEsc || DFLT_ESC_AUTO_SUPER == nEsc) + m_aStyles.append(OOO_STRING_SVTOOLS_RTF_SUPER); + return; + } + if (DFLT_ESC_AUTO_SUPER == nEsc) + { + nEsc = .8 * (100 - nProp); + ++nProp100; // A 1 afterwards means 'automatic' according to editeng/rtf/rtfitem.cxx + } + else if (DFLT_ESC_AUTO_SUB == nEsc) + { + nEsc = .2 * -(100 - nProp); + ++nProp100; + } + + const char* pUpDn; + + double fHeight = m_rExport.GetItem(RES_CHRATR_FONTSIZE).GetHeight(); + + if (0 < nEsc) + pUpDn = OOO_STRING_SVTOOLS_RTF_UP; + else if (0 > nEsc) + { + pUpDn = OOO_STRING_SVTOOLS_RTF_DN; + fHeight = -fHeight; + } + else + return; + + m_aStyles.append('{'); + m_aStyles.append(OOO_STRING_SVTOOLS_RTF_IGNORE); + m_aStyles.append(OOO_STRING_SVTOOLS_RTF_UPDNPROP); + m_aStyles.append(nProp100); + m_aStyles.append('}'); + m_aStyles.append(pUpDn); + + /* + * Calculate the act. FontSize and the percentage of the displacement; + * RTF file expects half points, while internally it's in twips. + * Formally : (FontSize * 1/20 ) pts x * 2 + * ----------------------- = ------------ + * 100% Escapement + */ + m_aStyles.append(static_cast<sal_Int32>(round(fHeight * nEsc / 1000))); +} + +void RtfAttributeOutput::CharFont(const SvxFontItem& rFont) +{ + // Insert \loch in MoveCharacterProperties + m_aStyles.append(OOO_STRING_SVTOOLS_RTF_F); + m_aStyles.append(static_cast<sal_Int32>(m_rExport.m_aFontHelper.GetId(rFont))); + + // Insert \hich in MoveCharacterProperties + m_aStylesAssocHich.append(OOO_STRING_SVTOOLS_RTF_AF); + m_aStylesAssocHich.append(static_cast<sal_Int32>(m_rExport.m_aFontHelper.GetId(rFont))); + + // FIXME: this may be a tad expensive... but the charset needs to be + // consistent with what wwFont::WriteRtf() does + sw::util::FontMapExport aTmp(rFont.GetFamilyName()); + sal_uInt8 nWindowsCharset = sw::ms::rtl_TextEncodingToWinCharsetRTF( + aTmp.msPrimary, aTmp.msSecondary, rFont.GetCharSet()); + m_rExport.SetCurrentEncoding(rtl_getTextEncodingFromWindowsCharset(nWindowsCharset)); + if (m_rExport.GetCurrentEncoding() == RTL_TEXTENCODING_DONTKNOW) + m_rExport.SetCurrentEncoding(m_rExport.GetDefaultEncoding()); +} + +void RtfAttributeOutput::CharFontSize(const SvxFontHeightItem& rFontSize) +{ + switch (rFontSize.Which()) + { + case RES_CHRATR_FONTSIZE: + m_aStyles.append(OOO_STRING_SVTOOLS_RTF_FS); + m_aStyles.append(static_cast<sal_Int32>(rFontSize.GetHeight() / 10)); + break; + case RES_CHRATR_CJK_FONTSIZE: + m_aStylesAssocDbch.append(OOO_STRING_SVTOOLS_RTF_AFS); + m_aStylesAssocDbch.append(static_cast<sal_Int32>(rFontSize.GetHeight() / 10)); + break; + case RES_CHRATR_CTL_FONTSIZE: + m_aStylesAssocRtlch.append(OOO_STRING_SVTOOLS_RTF_AFS); + m_aStylesAssocRtlch.append(static_cast<sal_Int32>(rFontSize.GetHeight() / 10)); + break; + } +} + +void RtfAttributeOutput::CharKerning(const SvxKerningItem& rKerning) +{ + // in quarter points then in twips + m_aStyles.append(OOO_STRING_SVTOOLS_RTF_EXPND); + m_aStyles.append(static_cast<sal_Int32>(rKerning.GetValue() / 5)); + m_aStyles.append(OOO_STRING_SVTOOLS_RTF_EXPNDTW); + m_aStyles.append(static_cast<sal_Int32>(rKerning.GetValue())); +} + +void RtfAttributeOutput::CharLanguage(const SvxLanguageItem& rLanguage) +{ + switch (rLanguage.Which()) + { + case RES_CHRATR_LANGUAGE: + m_aStyles.append(OOO_STRING_SVTOOLS_RTF_LANG); + m_aStyles.append( + static_cast<sal_Int32>(static_cast<sal_uInt16>(rLanguage.GetLanguage()))); + m_aStylesAssocLtrch.append(OOO_STRING_SVTOOLS_RTF_LANG); + m_aStylesAssocLtrch.append( + static_cast<sal_Int32>(static_cast<sal_uInt16>(rLanguage.GetLanguage()))); + break; + case RES_CHRATR_CJK_LANGUAGE: + m_aStylesAssocDbch.append(OOO_STRING_SVTOOLS_RTF_LANGFE); + m_aStylesAssocDbch.append( + static_cast<sal_Int32>(static_cast<sal_uInt16>(rLanguage.GetLanguage()))); + m_aStylesAssocLtrch.append(OOO_STRING_SVTOOLS_RTF_LANGFE); + m_aStylesAssocLtrch.append( + static_cast<sal_Int32>(static_cast<sal_uInt16>(rLanguage.GetLanguage()))); + break; + case RES_CHRATR_CTL_LANGUAGE: + m_aStylesAssocRtlch.append(OOO_STRING_SVTOOLS_RTF_ALANG); + m_aStylesAssocRtlch.append( + static_cast<sal_Int32>(static_cast<sal_uInt16>(rLanguage.GetLanguage()))); + break; + } +} + +void RtfAttributeOutput::CharPosture(const SvxPostureItem& rPosture) +{ + m_aStyles.append(OOO_STRING_SVTOOLS_RTF_I); + if (rPosture.GetPosture() == ITALIC_NONE) + m_aStyles.append(sal_Int32(0)); +} + +void RtfAttributeOutput::CharShadow(const SvxShadowedItem& rShadow) +{ + m_aStyles.append(OOO_STRING_SVTOOLS_RTF_SHAD); + if (!rShadow.GetValue()) + m_aStyles.append(sal_Int32(0)); +} + +void RtfAttributeOutput::CharUnderline(const SvxUnderlineItem& rUnderline) +{ + const char* pStr = nullptr; + const SfxPoolItem* pItem = m_rExport.HasItem(RES_CHRATR_WORDLINEMODE); + bool bWord = false; + // No StaticWhichCast(RES_CHRATR_WORDLINEMODE), this may be for a postit, where the which ids + // don't match. + if (pItem) + bWord = static_cast<const SvxWordLineModeItem*>(pItem)->GetValue(); + switch (rUnderline.GetLineStyle()) + { + case LINESTYLE_SINGLE: + pStr = bWord ? OOO_STRING_SVTOOLS_RTF_ULW : OOO_STRING_SVTOOLS_RTF_UL; + break; + case LINESTYLE_DOUBLE: + pStr = OOO_STRING_SVTOOLS_RTF_ULDB; + break; + case LINESTYLE_NONE: + pStr = OOO_STRING_SVTOOLS_RTF_ULNONE; + break; + case LINESTYLE_DOTTED: + pStr = OOO_STRING_SVTOOLS_RTF_ULD; + break; + case LINESTYLE_DASH: + pStr = OOO_STRING_SVTOOLS_RTF_ULDASH; + break; + case LINESTYLE_DASHDOT: + pStr = OOO_STRING_SVTOOLS_RTF_ULDASHD; + break; + case LINESTYLE_DASHDOTDOT: + pStr = OOO_STRING_SVTOOLS_RTF_ULDASHDD; + break; + case LINESTYLE_BOLD: + pStr = OOO_STRING_SVTOOLS_RTF_ULTH; + break; + case LINESTYLE_WAVE: + pStr = OOO_STRING_SVTOOLS_RTF_ULWAVE; + break; + case LINESTYLE_BOLDDOTTED: + pStr = OOO_STRING_SVTOOLS_RTF_ULTHD; + break; + case LINESTYLE_BOLDDASH: + pStr = OOO_STRING_SVTOOLS_RTF_ULTHDASH; + break; + case LINESTYLE_LONGDASH: + pStr = OOO_STRING_SVTOOLS_RTF_ULLDASH; + break; + case LINESTYLE_BOLDLONGDASH: + pStr = OOO_STRING_SVTOOLS_RTF_ULTHLDASH; + break; + case LINESTYLE_BOLDDASHDOT: + pStr = OOO_STRING_SVTOOLS_RTF_ULTHDASHD; + break; + case LINESTYLE_BOLDDASHDOTDOT: + pStr = OOO_STRING_SVTOOLS_RTF_ULTHDASHDD; + break; + case LINESTYLE_BOLDWAVE: + pStr = OOO_STRING_SVTOOLS_RTF_ULHWAVE; + break; + case LINESTYLE_DOUBLEWAVE: + pStr = OOO_STRING_SVTOOLS_RTF_ULULDBWAVE; + break; + default: + break; + } + + if (pStr) + { + m_aStyles.append(pStr); + // NEEDSWORK looks like here rUnderline.GetColor() is always black, + // even if the color in the odt is for example green... + m_aStyles.append(OOO_STRING_SVTOOLS_RTF_ULC); + m_aStyles.append(static_cast<sal_Int32>(m_rExport.GetColor(rUnderline.GetColor()))); + } +} + +void RtfAttributeOutput::CharWeight(const SvxWeightItem& rWeight) +{ + m_aStyles.append(OOO_STRING_SVTOOLS_RTF_B); + if (rWeight.GetWeight() != WEIGHT_BOLD) + m_aStyles.append(sal_Int32(0)); +} + +void RtfAttributeOutput::CharAutoKern(const SvxAutoKernItem& rAutoKern) +{ + m_aStyles.append(OOO_STRING_SVTOOLS_RTF_KERNING); + m_aStyles.append(static_cast<sal_Int32>(rAutoKern.GetValue() ? 1 : 0)); +} + +void RtfAttributeOutput::CharAnimatedText(const SvxBlinkItem& rBlink) +{ + m_aStyles.append(OOO_STRING_SVTOOLS_RTF_ANIMTEXT); + m_aStyles.append(static_cast<sal_Int32>(rBlink.GetValue() ? 2 : 0)); +} + +void RtfAttributeOutput::CharBackground(const SvxBrushItem& rBrush) +{ + if (!rBrush.GetColor().IsTransparent()) + { + m_aStyles.append(OOO_STRING_SVTOOLS_RTF_CHCBPAT); + m_aStyles.append(static_cast<sal_Int32>(m_rExport.GetColor(rBrush.GetColor()))); + } +} + +void RtfAttributeOutput::CharFontCJK(const SvxFontItem& rFont) +{ + // Insert \dbch in MoveCharacterProperties + m_aStylesAssocDbch.append(OOO_STRING_SVTOOLS_RTF_AF); + m_aStylesAssocDbch.append(static_cast<sal_Int32>(m_rExport.m_aFontHelper.GetId(rFont))); +} + +void RtfAttributeOutput::CharFontSizeCJK(const SvxFontHeightItem& rFontSize) +{ + CharFontSize(rFontSize); +} + +void RtfAttributeOutput::CharLanguageCJK(const SvxLanguageItem& rLanguageItem) +{ + CharLanguage(rLanguageItem); +} + +void RtfAttributeOutput::CharPostureCJK(const SvxPostureItem& rPosture) +{ + m_aStylesAssocDbch.append(OOO_STRING_SVTOOLS_RTF_I); + if (rPosture.GetPosture() == ITALIC_NONE) + m_aStylesAssocDbch.append(sal_Int32(0)); +} + +void RtfAttributeOutput::CharWeightCJK(const SvxWeightItem& rWeight) +{ + m_aStylesAssocDbch.append(OOO_STRING_SVTOOLS_RTF_AB); + if (rWeight.GetWeight() != WEIGHT_BOLD) + m_aStylesAssocDbch.append(sal_Int32(0)); +} + +void RtfAttributeOutput::CharFontCTL(const SvxFontItem& rFont) +{ + // Insert \rtlch in MoveCharacterProperties + m_aStylesAssocRtlch.append(OOO_STRING_SVTOOLS_RTF_AF); + m_aStylesAssocRtlch.append(static_cast<sal_Int32>(m_rExport.m_aFontHelper.GetId(rFont))); +} + +void RtfAttributeOutput::CharFontSizeCTL(const SvxFontHeightItem& rFontSize) +{ + CharFontSize(rFontSize); +} + +void RtfAttributeOutput::CharLanguageCTL(const SvxLanguageItem& rLanguageItem) +{ + CharLanguage(rLanguageItem); +} + +void RtfAttributeOutput::CharPostureCTL(const SvxPostureItem& rPosture) +{ + m_aStylesAssocRtlch.append(OOO_STRING_SVTOOLS_RTF_AI); + if (rPosture.GetPosture() == ITALIC_NONE) + m_aStylesAssocRtlch.append(sal_Int32(0)); +} + +void RtfAttributeOutput::CharWeightCTL(const SvxWeightItem& rWeight) +{ + m_aStylesAssocRtlch.append(OOO_STRING_SVTOOLS_RTF_AB); + if (rWeight.GetWeight() != WEIGHT_BOLD) + m_aStylesAssocRtlch.append(sal_Int32(0)); +} + +void RtfAttributeOutput::CharBidiRTL(const SfxPoolItem& /*rItem*/) {} + +void RtfAttributeOutput::CharIdctHint(const SfxPoolItem& /*rItem*/) {} + +void RtfAttributeOutput::CharRotate(const SvxCharRotateItem& rRotate) +{ + m_aStyles.append(OOO_STRING_SVTOOLS_RTF_HORZVERT); + m_aStyles.append(static_cast<sal_Int32>(rRotate.IsFitToLine() ? 1 : 0)); +} + +void RtfAttributeOutput::CharEmphasisMark(const SvxEmphasisMarkItem& rEmphasisMark) +{ + FontEmphasisMark v = rEmphasisMark.GetEmphasisMark(); + if (v == FontEmphasisMark::NONE) + m_aStyles.append(OOO_STRING_SVTOOLS_RTF_ACCNONE); + else if (v == (FontEmphasisMark::Dot | FontEmphasisMark::PosAbove)) + m_aStyles.append(OOO_STRING_SVTOOLS_RTF_ACCDOT); + else if (v == (FontEmphasisMark::Accent | FontEmphasisMark::PosAbove)) + m_aStyles.append(OOO_STRING_SVTOOLS_RTF_ACCCOMMA); + else if (v == (FontEmphasisMark::Circle | FontEmphasisMark::PosAbove)) + m_aStyles.append(OOO_STRING_SVTOOLS_RTF_ACCCIRCLE); + else if (v == (FontEmphasisMark::Dot | FontEmphasisMark::PosBelow)) + m_aStyles.append(OOO_STRING_SVTOOLS_RTF_ACCUNDERDOT); +} + +void RtfAttributeOutput::CharTwoLines(const SvxTwoLinesItem& rTwoLines) +{ + if (!rTwoLines.GetValue()) + return; + + sal_Unicode cStart = rTwoLines.GetStartBracket(); + sal_Unicode cEnd = rTwoLines.GetEndBracket(); + + sal_uInt16 nType; + if (!cStart && !cEnd) + nType = 0; + else if ('{' == cStart || '}' == cEnd) + nType = 4; + else if ('<' == cStart || '>' == cEnd) + nType = 3; + else if ('[' == cStart || ']' == cEnd) + nType = 2; + else // all other kind of brackets + nType = 1; + + m_aStyles.append(OOO_STRING_SVTOOLS_RTF_TWOINONE); + m_aStyles.append(static_cast<sal_Int32>(nType)); +} + +void RtfAttributeOutput::CharScaleWidth(const SvxCharScaleWidthItem& rScaleWidth) +{ + m_aStyles.append(OOO_STRING_SVTOOLS_RTF_CHARSCALEX); + m_aStyles.append(static_cast<sal_Int32>(rScaleWidth.GetValue())); +} + +void RtfAttributeOutput::CharRelief(const SvxCharReliefItem& rRelief) +{ + const char* pStr; + switch (rRelief.GetValue()) + { + case FontRelief::Embossed: + pStr = OOO_STRING_SVTOOLS_RTF_EMBO; + break; + case FontRelief::Engraved: + pStr = OOO_STRING_SVTOOLS_RTF_IMPR; + break; + default: + pStr = nullptr; + break; + } + + if (pStr) + m_aStyles.append(pStr); +} + +void RtfAttributeOutput::CharHidden(const SvxCharHiddenItem& rHidden) +{ + m_aStyles.append(OOO_STRING_SVTOOLS_RTF_V); + if (!rHidden.GetValue()) + m_aStyles.append(sal_Int32(0)); +} + +void RtfAttributeOutput::CharBorder(const editeng::SvxBorderLine* pAllBorder, + const sal_uInt16 nDist, const bool bShadow) +{ + m_aStyles.append( + OutBorderLine(m_rExport, pAllBorder, OOO_STRING_SVTOOLS_RTF_CHBRDR, nDist, + bShadow ? SvxShadowLocation::BottomRight : SvxShadowLocation::NONE)); +} + +void RtfAttributeOutput::CharHighlight(const SvxBrushItem& rBrush) +{ + m_aStyles.append(OOO_STRING_SVTOOLS_RTF_HIGHLIGHT); + m_aStyles.append(static_cast<sal_Int32>(msfilter::util::TransColToIco(rBrush.GetColor()))); +} + +void RtfAttributeOutput::TextINetFormat(const SwFormatINetFormat& rURL) +{ + if (rURL.GetValue().isEmpty()) + return; + + const SwCharFormat* pFormat; + const SwTextINetFormat* pTextAtr = rURL.GetTextINetFormat(); + + if (pTextAtr && nullptr != (pFormat = pTextAtr->GetCharFormat())) + { + sal_uInt16 nStyle = m_rExport.GetId(pFormat); + OString* pString = m_rExport.GetStyle(nStyle); + if (pString) + m_aStyles.append(*pString); + } +} + +void RtfAttributeOutput::TextCharFormat(const SwFormatCharFormat& rCharFormat) +{ + sal_uInt16 nStyle = m_rExport.GetId(rCharFormat.GetCharFormat()); + m_aStyles.append(OOO_STRING_SVTOOLS_RTF_CS); + m_aStyles.append(static_cast<sal_Int32>(nStyle)); + OString* pString = m_rExport.GetStyle(nStyle); + if (pString) + m_aStyles.append(*pString); +} + +void RtfAttributeOutput::WriteTextFootnoteNumStr(const SwFormatFootnote& rFootnote) +{ + if (rFootnote.GetNumStr().isEmpty()) + m_aRun->append(OOO_STRING_SVTOOLS_RTF_CHFTN); + else + m_aRun->append( + msfilter::rtfutil::OutString(rFootnote.GetNumStr(), m_rExport.GetCurrentEncoding())); +} + +void RtfAttributeOutput::TextFootnote_Impl(const SwFormatFootnote& rFootnote) +{ + SAL_INFO("sw.rtf", __func__ << " start"); + + m_aRun->append("{" OOO_STRING_SVTOOLS_RTF_SUPER " "); + EndRunProperties(nullptr); + m_aRun->append(' '); + WriteTextFootnoteNumStr(rFootnote); + m_aRun->append("{" OOO_STRING_SVTOOLS_RTF_IGNORE OOO_STRING_SVTOOLS_RTF_FOOTNOTE); + if (rFootnote.IsEndNote() || m_rExport.m_rDoc.GetFootnoteInfo().m_ePos == FTNPOS_CHAPTER) + m_aRun->append(OOO_STRING_SVTOOLS_RTF_FTNALT); + m_aRun->append(' '); + WriteTextFootnoteNumStr(rFootnote); + + /* + * The footnote contains a whole paragraph, so we have to: + * 1) Reset, then later restore the contents of our run buffer and run state. + * 2) Buffer the output of the whole paragraph, as we do so for section headers already. + */ + const SwNodeIndex* pIndex = rFootnote.GetTextFootnote()->GetStartNode(); + RtfStringBuffer aRun = m_aRun; + m_aRun.clear(); + bool bInRunOrig = m_bInRun; + m_bInRun = false; + bool bSingleEmptyRunOrig = m_bSingleEmptyRun; + m_bSingleEmptyRun = false; + m_bBufferSectionHeaders = true; + m_rExport.WriteSpecialText(pIndex->GetIndex() + 1, pIndex->GetNode().EndOfSectionIndex(), + !rFootnote.IsEndNote() ? TXT_FTN : TXT_EDN); + m_bBufferSectionHeaders = false; + m_bInRun = bInRunOrig; + m_bSingleEmptyRun = bSingleEmptyRunOrig; + m_aRun = aRun; + m_aRun->append(m_aSectionHeaders.makeStringAndClear()); + + m_aRun->append("}"); + m_aRun->append("}"); + + SAL_INFO("sw.rtf", __func__ << " end"); +} + +void RtfAttributeOutput::ParaLineSpacing_Impl(short nSpace, short nMulti) +{ + m_aStyles.append(OOO_STRING_SVTOOLS_RTF_SL); + m_aStyles.append(static_cast<sal_Int32>(nSpace)); + m_aStyles.append(OOO_STRING_SVTOOLS_RTF_SLMULT); + m_aStyles.append(static_cast<sal_Int32>(nMulti)); +} + +void RtfAttributeOutput::ParaAdjust(const SvxAdjustItem& rAdjust) +{ + switch (rAdjust.GetAdjust()) + { + case SvxAdjust::Left: + m_aStyles.append(OOO_STRING_SVTOOLS_RTF_QL); + break; + case SvxAdjust::Right: + m_aStyles.append(OOO_STRING_SVTOOLS_RTF_QR); + break; + case SvxAdjust::BlockLine: + case SvxAdjust::Block: + if (rAdjust.GetLastBlock() == SvxAdjust::Block) + m_aStyles.append(OOO_STRING_SVTOOLS_RTF_QD); + else + m_aStyles.append(OOO_STRING_SVTOOLS_RTF_QJ); + break; + case SvxAdjust::Center: + m_aStyles.append(OOO_STRING_SVTOOLS_RTF_QC); + break; + default: + break; + } +} + +void RtfAttributeOutput::ParaSplit(const SvxFormatSplitItem& rSplit) +{ + if (!rSplit.GetValue()) + m_aStyles.append(OOO_STRING_SVTOOLS_RTF_KEEP); +} + +void RtfAttributeOutput::ParaWidows(const SvxWidowsItem& rWidows) +{ + if (rWidows.GetValue()) + m_aStyles.append(OOO_STRING_SVTOOLS_RTF_WIDCTLPAR); + else + m_aStyles.append(OOO_STRING_SVTOOLS_RTF_NOWIDCTLPAR); +} + +void RtfAttributeOutput::ParaTabStop(const SvxTabStopItem& rTabStop) +{ + tools::Long nOffset = 0; + // Tabs are absolute by default. + if (m_rExport.m_rDoc.getIDocumentSettingAccess().get( + DocumentSettingId::TABS_RELATIVE_TO_INDENT)) + nOffset = m_rExport.GetItem(RES_LR_SPACE).GetTextLeft(); + + for (sal_uInt16 n = 0; n < rTabStop.Count(); n++) + { + const SvxTabStop& rTS = rTabStop[n]; + if (SvxTabAdjust::Default != rTS.GetAdjustment()) + { + const char* pFill = nullptr; + switch (rTS.GetFill()) + { + case cDfltFillChar: + break; + + case '.': + pFill = OOO_STRING_SVTOOLS_RTF_TLDOT; + break; + case '_': + pFill = OOO_STRING_SVTOOLS_RTF_TLUL; + break; + case '-': + pFill = OOO_STRING_SVTOOLS_RTF_TLTH; + break; + case '=': + pFill = OOO_STRING_SVTOOLS_RTF_TLEQ; + break; + default: + break; + } + if (pFill) + m_aStyles.append(pFill); + + const char* pAdjStr = nullptr; + switch (rTS.GetAdjustment()) + { + case SvxTabAdjust::Right: + pAdjStr = OOO_STRING_SVTOOLS_RTF_TQR; + break; + case SvxTabAdjust::Decimal: + pAdjStr = OOO_STRING_SVTOOLS_RTF_TQDEC; + break; + case SvxTabAdjust::Center: + pAdjStr = OOO_STRING_SVTOOLS_RTF_TQC; + break; + default: + break; + } + if (pAdjStr) + m_aStyles.append(pAdjStr); + m_aStyles.append(OOO_STRING_SVTOOLS_RTF_TX); + m_aStyles.append(static_cast<sal_Int32>(rTS.GetTabPos() + nOffset)); + } + else + { + m_aTabStop.append(OOO_STRING_SVTOOLS_RTF_DEFTAB); + m_aTabStop.append(rTabStop[0].GetTabPos()); + } + } +} + +void RtfAttributeOutput::ParaHyphenZone(const SvxHyphenZoneItem& rHyphenZone) +{ + m_aStyles.append(OOO_STRING_SVTOOLS_RTF_HYPHPAR); + m_aStyles.append(sal_Int32(rHyphenZone.IsHyphen())); +} + +void RtfAttributeOutput::ParaNumRule_Impl(const SwTextNode* pTextNd, sal_Int32 nLvl, + sal_Int32 nNumId) +{ + if (USHRT_MAX == nNumId || 0 == nNumId || nullptr == pTextNd) + return; + + const SwNumRule* pRule = pTextNd->GetNumRule(); + + if (!pRule || !pTextNd->IsInList()) + return; + + SAL_WARN_IF(pTextNd->GetActualListLevel() < 0 || pTextNd->GetActualListLevel() >= MAXLEVEL, + "sw.rtf", "text node does not have valid list level"); + + const SwNumFormat* pFormat = pRule->GetNumFormat(nLvl); + if (!pFormat) + pFormat = &pRule->Get(nLvl); + + const SfxItemSet& rNdSet = pTextNd->GetSwAttrSet(); + + m_aStyles.append('{'); + m_aStyles.append(OOO_STRING_SVTOOLS_RTF_LISTTEXT); + m_aStyles.append(OOO_STRING_SVTOOLS_RTF_PARD); + m_aStyles.append(OOO_STRING_SVTOOLS_RTF_PLAIN); + m_aStyles.append(' '); + + SvxLRSpaceItem aLR(rNdSet.Get(RES_LR_SPACE)); + aLR.SetTextLeft(aLR.GetTextLeft() + pFormat->GetIndentAt()); + aLR.SetTextFirstLineOffset(pFormat->GetFirstLineOffset()); //TODO: overflow + + sal_uInt16 nStyle = m_rExport.GetId(pFormat->GetCharFormat()); + OString* pString = m_rExport.GetStyle(nStyle); + if (pString) + m_aStyles.append(*pString); + + { + OUString sText; + if (SVX_NUM_CHAR_SPECIAL == pFormat->GetNumberingType() + || SVX_NUM_BITMAP == pFormat->GetNumberingType()) + { + sal_UCS4 cBullet = pFormat->GetBulletChar(); + sText = OUString(&cBullet, 1); + } + else + sText = pTextNd->GetNumString(); + + if (!sText.isEmpty()) + { + m_aStyles.append(' '); + m_aStyles.append(msfilter::rtfutil::OutString(sText, m_rExport.GetDefaultEncoding())); + } + + if (OUTLINE_RULE != pRule->GetRuleType()) + { + if (!sText.isEmpty()) + m_aStyles.append(OOO_STRING_SVTOOLS_RTF_TAB); + m_aStyles.append('}'); + m_aStyles.append(OOO_STRING_SVTOOLS_RTF_ILVL); + if (nLvl > 8) // RTF knows only 9 levels + { + m_aStyles.append(sal_Int32(8)); + m_aStyles.append("{" OOO_STRING_SVTOOLS_RTF_IGNORE OOO_STRING_SVTOOLS_RTF_SOUTLVL); + m_aStyles.append(nLvl); + m_aStyles.append('}'); + } + else + m_aStyles.append(nLvl); + } + else + m_aStyles.append(OOO_STRING_SVTOOLS_RTF_TAB "}"); + m_aStyles.append(OOO_STRING_SVTOOLS_RTF_LS); + m_aStyles.append(static_cast<sal_Int32>(m_rExport.GetNumberingId(*pRule)) + 1); + m_aStyles.append(' '); + } + FormatLRSpace(aLR); +} + +void RtfAttributeOutput::ParaScriptSpace(const SfxBoolItem& rScriptSpace) +{ + if (!rScriptSpace.GetValue()) + return; + + m_aStyles.append(OOO_STRING_SVTOOLS_RTF_ASPALPHA); +} + +void RtfAttributeOutput::ParaHangingPunctuation(const SfxBoolItem& /*rItem*/) +{ + SAL_INFO("sw.rtf", "TODO: " << __func__); +} + +void RtfAttributeOutput::ParaForbiddenRules(const SfxBoolItem& /*rItem*/) +{ + SAL_INFO("sw.rtf", "TODO: " << __func__); +} + +void RtfAttributeOutput::ParaVerticalAlign(const SvxParaVertAlignItem& rAlign) +{ + const char* pStr; + switch (rAlign.GetValue()) + { + case SvxParaVertAlignItem::Align::Top: + pStr = OOO_STRING_SVTOOLS_RTF_FAHANG; + break; + case SvxParaVertAlignItem::Align::Bottom: + pStr = OOO_STRING_SVTOOLS_RTF_FAVAR; + break; + case SvxParaVertAlignItem::Align::Center: + pStr = OOO_STRING_SVTOOLS_RTF_FACENTER; + break; + case SvxParaVertAlignItem::Align::Baseline: + pStr = OOO_STRING_SVTOOLS_RTF_FAROMAN; + break; + + default: + pStr = OOO_STRING_SVTOOLS_RTF_FAAUTO; + break; + } + m_aStyles.append(pStr); +} + +void RtfAttributeOutput::ParaSnapToGrid(const SvxParaGridItem& /*rGrid*/) +{ + SAL_INFO("sw.rtf", "TODO: " << __func__); +} + +void RtfAttributeOutput::FormatFrameSize(const SwFormatFrameSize& rSize) +{ + if (m_rExport.m_bOutPageDescs) + { + m_aSectionBreaks.append(OOO_STRING_SVTOOLS_RTF_PGWSXN); + m_aSectionBreaks.append(static_cast<sal_Int32>(rSize.GetWidth())); + m_aSectionBreaks.append(OOO_STRING_SVTOOLS_RTF_PGHSXN); + m_aSectionBreaks.append(static_cast<sal_Int32>(rSize.GetHeight())); + if (!m_bBufferSectionBreaks) + m_rExport.Strm().WriteOString(m_aSectionBreaks.makeStringAndClear()); + } +} + +void RtfAttributeOutput::FormatPaperBin(const SvxPaperBinItem& /*rItem*/) +{ + SAL_INFO("sw.rtf", "TODO: " << __func__); +} + +void RtfAttributeOutput::FormatLRSpace(const SvxLRSpaceItem& rLRSpace) +{ + if (!m_rExport.m_bOutFlyFrameAttrs) + { + if (m_rExport.m_bOutPageDescs) + { + m_aPageMargins.nLeft = 0; + m_aPageMargins.nRight = 0; + + if (const SvxBoxItem* pBoxItem = m_rExport.HasItem(RES_BOX)) + { + m_aPageMargins.nLeft + = pBoxItem->CalcLineSpace(SvxBoxItemLine::LEFT, /*bEvenIfNoLine*/ true); + m_aPageMargins.nRight + = pBoxItem->CalcLineSpace(SvxBoxItemLine::RIGHT, /*bEvenIfNoLine*/ true); + } + + m_aPageMargins.nLeft += sal::static_int_cast<sal_uInt16>(rLRSpace.GetLeft()); + m_aPageMargins.nRight += sal::static_int_cast<sal_uInt16>(rLRSpace.GetRight()); + + if (rLRSpace.GetLeft()) + { + m_aSectionBreaks.append(OOO_STRING_SVTOOLS_RTF_MARGLSXN); + m_aSectionBreaks.append(static_cast<sal_Int32>(m_aPageMargins.nLeft)); + } + if (rLRSpace.GetRight()) + { + m_aSectionBreaks.append(OOO_STRING_SVTOOLS_RTF_MARGRSXN); + m_aSectionBreaks.append(static_cast<sal_Int32>(m_aPageMargins.nRight)); + } + if (rLRSpace.GetGutterMargin()) + { + m_aSectionBreaks.append(OOO_STRING_SVTOOLS_RTF_GUTTER); + m_aSectionBreaks.append(static_cast<sal_Int32>(rLRSpace.GetGutterMargin())); + } + if (!m_bBufferSectionBreaks) + m_rExport.Strm().WriteOString(m_aSectionBreaks.makeStringAndClear()); + } + else + { + m_aStyles.append(OOO_STRING_SVTOOLS_RTF_LI); + m_aStyles.append(static_cast<sal_Int32>(rLRSpace.GetTextLeft())); + m_aStyles.append(OOO_STRING_SVTOOLS_RTF_RI); + m_aStyles.append(static_cast<sal_Int32>(rLRSpace.GetRight())); + m_aStyles.append(OOO_STRING_SVTOOLS_RTF_LIN); + m_aStyles.append(static_cast<sal_Int32>(rLRSpace.GetTextLeft())); + m_aStyles.append(OOO_STRING_SVTOOLS_RTF_RIN); + m_aStyles.append(static_cast<sal_Int32>(rLRSpace.GetRight())); + m_aStyles.append(OOO_STRING_SVTOOLS_RTF_FI); + m_aStyles.append(static_cast<sal_Int32>(rLRSpace.GetTextFirstLineOffset())); + } + } + else if (m_rExport.GetRTFFlySyntax()) + { + // Wrap: top and bottom spacing, convert from twips to EMUs. + m_aFlyProperties.push_back(std::make_pair<OString, OString>( + "dxWrapDistLeft", OString::number(o3tl::convert(rLRSpace.GetLeft(), o3tl::Length::twip, + o3tl::Length::emu)))); + m_aFlyProperties.push_back(std::make_pair<OString, OString>( + "dxWrapDistRight", OString::number(o3tl::convert( + rLRSpace.GetRight(), o3tl::Length::twip, o3tl::Length::emu)))); + } +} + +void RtfAttributeOutput::FormatULSpace(const SvxULSpaceItem& rULSpace) +{ + if (!m_rExport.m_bOutFlyFrameAttrs) + { + if (m_rExport.m_bOutPageDescs) + { + OSL_ENSURE(m_rExport.GetCurItemSet(), "Impossible"); + if (!m_rExport.GetCurItemSet()) + return; + + // If we export a follow page format, then our doc model has + // separate header/footer distances for the first page and the + // follow pages, but Word can have only a single distance. In case + // the two values differ, work with the value from the first page + // format to be in sync with the import. + sw::util::HdFtDistanceGlue aDistances(m_rExport.GetFirstPageItemSet() + ? *m_rExport.GetFirstPageItemSet() + : *m_rExport.GetCurItemSet()); + + if (aDistances.m_DyaTop) + { + m_aSectionBreaks.append(OOO_STRING_SVTOOLS_RTF_MARGTSXN); + m_aSectionBreaks.append(static_cast<sal_Int32>(aDistances.m_DyaTop)); + m_aPageMargins.nTop = aDistances.m_DyaTop; + } + if (aDistances.HasHeader()) + { + m_aSectionBreaks.append(OOO_STRING_SVTOOLS_RTF_HEADERY); + m_aSectionBreaks.append(static_cast<sal_Int32>(aDistances.m_DyaHdrTop)); + } + + if (aDistances.m_DyaBottom) + { + m_aSectionBreaks.append(OOO_STRING_SVTOOLS_RTF_MARGBSXN); + m_aSectionBreaks.append(static_cast<sal_Int32>(aDistances.m_DyaBottom)); + m_aPageMargins.nBottom = aDistances.m_DyaBottom; + } + if (aDistances.HasFooter()) + { + m_aSectionBreaks.append(OOO_STRING_SVTOOLS_RTF_FOOTERY); + m_aSectionBreaks.append(static_cast<sal_Int32>(aDistances.m_DyaHdrBottom)); + } + if (!m_bBufferSectionBreaks) + m_rExport.Strm().WriteOString(m_aSectionBreaks.makeStringAndClear()); + } + else + { + // Spacing before. + if (m_bParaBeforeAutoSpacing && m_nParaBeforeSpacing == rULSpace.GetUpper()) + m_aStyles.append(LO_STRING_SVTOOLS_RTF_SBAUTO "1"); + else if (m_bParaBeforeAutoSpacing && m_nParaBeforeSpacing == -1) + { + m_aStyles.append(LO_STRING_SVTOOLS_RTF_SBAUTO "0"); + m_aStyles.append(OOO_STRING_SVTOOLS_RTF_SB); + m_aStyles.append(static_cast<sal_Int32>(rULSpace.GetUpper())); + } + else + { + m_aStyles.append(OOO_STRING_SVTOOLS_RTF_SB); + m_aStyles.append(static_cast<sal_Int32>(rULSpace.GetUpper())); + } + m_bParaBeforeAutoSpacing = false; + + // Spacing after. + if (m_bParaAfterAutoSpacing && m_nParaAfterSpacing == rULSpace.GetLower()) + m_aStyles.append(LO_STRING_SVTOOLS_RTF_SAAUTO "1"); + else if (m_bParaAfterAutoSpacing && m_nParaAfterSpacing == -1) + { + m_aStyles.append(LO_STRING_SVTOOLS_RTF_SAAUTO "0"); + m_aStyles.append(OOO_STRING_SVTOOLS_RTF_SA); + m_aStyles.append(static_cast<sal_Int32>(rULSpace.GetLower())); + } + else + { + m_aStyles.append(OOO_STRING_SVTOOLS_RTF_SA); + m_aStyles.append(static_cast<sal_Int32>(rULSpace.GetLower())); + } + m_bParaAfterAutoSpacing = false; + + // Contextual spacing. + if (rULSpace.GetContext()) + m_aStyles.append(OOO_STRING_SVTOOLS_RTF_CONTEXTUALSPACE); + } + } + else if (m_rExport.GetRTFFlySyntax()) + { + // Wrap: top and bottom spacing, convert from twips to EMUs. + m_aFlyProperties.push_back(std::make_pair<OString, OString>( + "dyWrapDistTop", OString::number(o3tl::convert(rULSpace.GetUpper(), o3tl::Length::twip, + o3tl::Length::emu)))); + m_aFlyProperties.push_back(std::make_pair<OString, OString>( + "dyWrapDistBottom", OString::number(o3tl::convert( + rULSpace.GetLower(), o3tl::Length::twip, o3tl::Length::emu)))); + } +} + +void RtfAttributeOutput::FormatSurround(const SwFormatSurround& rSurround) +{ + if (m_rExport.m_bOutFlyFrameAttrs && !m_rExport.GetRTFFlySyntax()) + { + css::text::WrapTextMode eSurround = rSurround.GetSurround(); + bool bGold = css::text::WrapTextMode_DYNAMIC == eSurround; + if (bGold) + eSurround = css::text::WrapTextMode_PARALLEL; + RTFSurround aMC(bGold, static_cast<sal_uInt8>(eSurround)); + m_aRunText->append(OOO_STRING_SVTOOLS_RTF_FLYMAINCNT); + m_aRunText->append(static_cast<sal_Int32>(aMC.GetValue())); + } + else if (m_rExport.m_bOutFlyFrameAttrs && m_rExport.GetRTFFlySyntax()) + { + // See DocxSdrExport::startDMLAnchorInline() for SwFormatSurround -> WR / WRK mappings. + sal_Int32 nWr = -1; + std::optional<sal_Int32> oWrk; + switch (rSurround.GetValue()) + { + case css::text::WrapTextMode_NONE: + nWr = 1; // top and bottom + break; + case css::text::WrapTextMode_THROUGH: + nWr = 3; // none + break; + case css::text::WrapTextMode_PARALLEL: + nWr = 2; // around + oWrk = 0; // both sides + break; + case css::text::WrapTextMode_DYNAMIC: + default: + nWr = 2; // around + oWrk = 3; // largest + break; + } + + if (rSurround.IsContour()) + nWr = 4; // tight + + m_rExport.Strm().WriteCharPtr(OOO_STRING_SVTOOLS_RTF_SHPWR); + m_rExport.OutLong(nWr); + if (oWrk) + { + m_rExport.Strm().WriteCharPtr(OOO_STRING_SVTOOLS_RTF_SHPWRK); + m_rExport.OutLong(*oWrk); + } + } +} + +void RtfAttributeOutput::FormatVertOrientation(const SwFormatVertOrient& rFlyVert) +{ + if (!(m_rExport.m_bOutFlyFrameAttrs && m_rExport.GetRTFFlySyntax())) + return; + + switch (rFlyVert.GetRelationOrient()) + { + case text::RelOrientation::PAGE_FRAME: + m_aFlyProperties.push_back( + std::make_pair<OString, OString>("posrelv", OString::number(1))); + break; + default: + m_aFlyProperties.push_back( + std::make_pair<OString, OString>("posrelv", OString::number(2))); + m_rExport.Strm() + .WriteCharPtr(OOO_STRING_SVTOOLS_RTF_SHPBYPARA) + .WriteCharPtr(OOO_STRING_SVTOOLS_RTF_SHPBYIGNORE); + break; + } + + switch (rFlyVert.GetVertOrient()) + { + case text::VertOrientation::TOP: + case text::VertOrientation::LINE_TOP: + m_aFlyProperties.push_back( + std::make_pair<OString, OString>("posv", OString::number(1))); + break; + case text::VertOrientation::BOTTOM: + case text::VertOrientation::LINE_BOTTOM: + m_aFlyProperties.push_back( + std::make_pair<OString, OString>("posv", OString::number(3))); + break; + case text::VertOrientation::CENTER: + case text::VertOrientation::LINE_CENTER: + m_aFlyProperties.push_back( + std::make_pair<OString, OString>("posv", OString::number(2))); + break; + default: + break; + } + + m_rExport.Strm().WriteCharPtr(OOO_STRING_SVTOOLS_RTF_SHPTOP); + m_rExport.OutLong(rFlyVert.GetPos()); + if (m_pFlyFrameSize) + { + m_rExport.Strm().WriteCharPtr(OOO_STRING_SVTOOLS_RTF_SHPBOTTOM); + m_rExport.OutLong(rFlyVert.GetPos() + m_pFlyFrameSize->Height()); + } +} + +void RtfAttributeOutput::FormatHorizOrientation(const SwFormatHoriOrient& rFlyHori) +{ + if (!(m_rExport.m_bOutFlyFrameAttrs && m_rExport.GetRTFFlySyntax())) + return; + + switch (rFlyHori.GetRelationOrient()) + { + case text::RelOrientation::PAGE_FRAME: + m_aFlyProperties.push_back( + std::make_pair<OString, OString>("posrelh", OString::number(1))); + break; + default: + m_aFlyProperties.push_back( + std::make_pair<OString, OString>("posrelh", OString::number(2))); + m_rExport.Strm() + .WriteCharPtr(OOO_STRING_SVTOOLS_RTF_SHPBXCOLUMN) + .WriteCharPtr(OOO_STRING_SVTOOLS_RTF_SHPBXIGNORE); + break; + } + + switch (rFlyHori.GetHoriOrient()) + { + case text::HoriOrientation::LEFT: + m_aFlyProperties.push_back( + std::make_pair<OString, OString>("posh", OString::number(1))); + break; + case text::HoriOrientation::CENTER: + m_aFlyProperties.push_back( + std::make_pair<OString, OString>("posh", OString::number(2))); + break; + case text::HoriOrientation::RIGHT: + m_aFlyProperties.push_back( + std::make_pair<OString, OString>("posh", OString::number(3))); + break; + default: + break; + } + + m_rExport.Strm().WriteCharPtr(OOO_STRING_SVTOOLS_RTF_SHPLEFT); + m_rExport.OutLong(rFlyHori.GetPos()); + if (m_pFlyFrameSize) + { + m_rExport.Strm().WriteCharPtr(OOO_STRING_SVTOOLS_RTF_SHPRIGHT); + m_rExport.OutLong(rFlyHori.GetPos() + m_pFlyFrameSize->Width()); + } +} + +void RtfAttributeOutput::FormatAnchor(const SwFormatAnchor& rAnchor) +{ + if (m_rExport.GetRTFFlySyntax()) + return; + + RndStdIds eId = rAnchor.GetAnchorId(); + m_aRunText->append(OOO_STRING_SVTOOLS_RTF_FLYANCHOR); + m_aRunText->append(static_cast<sal_Int32>(eId)); + switch (eId) + { + case RndStdIds::FLY_AT_PAGE: + m_aRunText->append(OOO_STRING_SVTOOLS_RTF_FLYPAGE); + m_aRunText->append(static_cast<sal_Int32>(rAnchor.GetPageNum())); + break; + case RndStdIds::FLY_AT_PARA: + case RndStdIds::FLY_AS_CHAR: + m_aRunText->append(OOO_STRING_SVTOOLS_RTF_FLYCNTNT); + break; + default: + break; + } +} + +void RtfAttributeOutput::FormatBackground(const SvxBrushItem& rBrush) +{ + if (m_rExport.GetRTFFlySyntax()) + { + const Color& rColor = rBrush.GetColor(); + // We in fact need RGB to BGR, but the transformation is symmetric. + m_aFlyProperties.push_back(std::make_pair<OString, OString>( + "fillColor", OString::number(wwUtility::RGBToBGR(rColor)))); + } + else if (!rBrush.GetColor().IsTransparent()) + { + m_aStyles.append(OOO_STRING_SVTOOLS_RTF_CBPAT); + m_aStyles.append(static_cast<sal_Int32>(m_rExport.GetColor(rBrush.GetColor()))); + } +} + +void RtfAttributeOutput::FormatFillStyle(const XFillStyleItem& rFillStyle) +{ + m_oFillStyle = rFillStyle.GetValue(); +} + +void RtfAttributeOutput::FormatFillGradient(const XFillGradientItem& rFillGradient) +{ + if (*m_oFillStyle != drawing::FillStyle_GRADIENT) + return; + + m_aFlyProperties.push_back(std::make_pair<OString, OString>( + "fillType", OString::number(7))); // Shade using the fillAngle + + const XGradient& rGradient = rFillGradient.GetGradientValue(); + const Color& rStartColor = rGradient.GetStartColor(); + m_aFlyProperties.push_back(std::make_pair<OString, OString>( + "fillBackColor", OString::number(wwUtility::RGBToBGR(rStartColor)))); + + const Color& rEndColor = rGradient.GetEndColor(); + m_aFlyProperties.push_back(std::make_pair<OString, OString>( + "fillColor", OString::number(wwUtility::RGBToBGR(rEndColor)))); + + switch (rGradient.GetGradientStyle()) + { + case css::awt::GradientStyle_LINEAR: + break; + case css::awt::GradientStyle_AXIAL: + m_aFlyProperties.push_back( + std::make_pair<OString, OString>("fillFocus", OString::number(50))); + break; + case css::awt::GradientStyle_RADIAL: + case css::awt::GradientStyle_ELLIPTICAL: + case css::awt::GradientStyle_SQUARE: + case css::awt::GradientStyle_RECT: + default: + break; + } +} + +void RtfAttributeOutput::FormatBox(const SvxBoxItem& rBox) +{ + static const SvxBoxItemLine aBorders[] = { SvxBoxItemLine::TOP, SvxBoxItemLine::LEFT, + SvxBoxItemLine::BOTTOM, SvxBoxItemLine::RIGHT }; + static const char* aBorderNames[] + = { OOO_STRING_SVTOOLS_RTF_BRDRT, OOO_STRING_SVTOOLS_RTF_BRDRL, + OOO_STRING_SVTOOLS_RTF_BRDRB, OOO_STRING_SVTOOLS_RTF_BRDRR }; + + sal_uInt16 const nDist = rBox.GetSmallestDistance(); + + if (m_rExport.GetRTFFlySyntax()) + { + // Borders: spacing to contents, convert from twips to EMUs. + m_aFlyProperties.push_back(std::make_pair<OString, OString>( + "dxTextLeft", OString::number(o3tl::convert(rBox.GetDistance(SvxBoxItemLine::LEFT), + o3tl::Length::twip, o3tl::Length::emu)))); + m_aFlyProperties.push_back(std::make_pair<OString, OString>( + "dyTextTop", OString::number(o3tl::convert(rBox.GetDistance(SvxBoxItemLine::TOP), + o3tl::Length::twip, o3tl::Length::emu)))); + m_aFlyProperties.push_back(std::make_pair<OString, OString>( + "dxTextRight", OString::number(o3tl::convert(rBox.GetDistance(SvxBoxItemLine::RIGHT), + o3tl::Length::twip, o3tl::Length::emu)))); + m_aFlyProperties.push_back(std::make_pair<OString, OString>( + "dyTextBottom", OString::number(o3tl::convert(rBox.GetDistance(SvxBoxItemLine::BOTTOM), + o3tl::Length::twip, o3tl::Length::emu)))); + + const editeng::SvxBorderLine* pLeft = rBox.GetLine(SvxBoxItemLine::LEFT); + const editeng::SvxBorderLine* pRight = rBox.GetLine(SvxBoxItemLine::RIGHT); + const editeng::SvxBorderLine* pTop = rBox.GetLine(SvxBoxItemLine::TOP); + const editeng::SvxBorderLine* pBottom = rBox.GetLine(SvxBoxItemLine::BOTTOM); + + if (!pLeft && !pRight && !pBottom && !pTop) + { + // fLine has default 'true', so need to write it out in case of no border. + m_aFlyProperties.push_back(std::make_pair<OString, OString>("fLine", "0")); + return; + } + + // RTF has the flags fTopLine, fBottomLine, fLeftLine and fRightLine to disable single border + // lines. But Word cannot disable single border lines. So we do not use them. In case of + // single border lines it is better to draw all four borders than drawing none. So we look + // whether a border line exists, which is effectively drawn. + const editeng::SvxBorderLine* pBorder = nullptr; + if (pTop && pTop->GetBorderLineStyle() != SvxBorderLineStyle::NONE) + pBorder = pTop; + else if (pBottom && pBottom->GetBorderLineStyle() != SvxBorderLineStyle::NONE) + pBorder = pBottom; + else if (pLeft && pLeft->GetBorderLineStyle() != SvxBorderLineStyle::NONE) + pBorder = pLeft; + else if (pRight && pRight->GetBorderLineStyle() != SvxBorderLineStyle::NONE) + pBorder = pRight; + + if (!pBorder) + { + m_aFlyProperties.push_back(std::make_pair<OString, OString>("fLine", "0")); + return; + } + + const Color& rColor = pBorder->GetColor(); + // We in fact need RGB to BGR, but the transformation is symmetric. + m_aFlyProperties.push_back(std::make_pair<OString, OString>( + "lineColor", OString::number(wwUtility::RGBToBGR(rColor)))); + + double const fConverted( + editeng::ConvertBorderWidthToWord(pBorder->GetBorderLineStyle(), pBorder->GetWidth())); + sal_Int32 nWidth = o3tl::convert(fConverted, o3tl::Length::twip, o3tl::Length::emu); + m_aFlyProperties.push_back( + std::make_pair<OString, OString>("lineWidth", OString::number(nWidth))); + + return; + } + + if (rBox.GetTop() && rBox.GetBottom() && rBox.GetLeft() && rBox.GetRight() + && *rBox.GetTop() == *rBox.GetBottom() && *rBox.GetTop() == *rBox.GetLeft() + && *rBox.GetTop() == *rBox.GetRight() && nDist == rBox.GetDistance(SvxBoxItemLine::TOP) + && nDist == rBox.GetDistance(SvxBoxItemLine::LEFT) + && nDist == rBox.GetDistance(SvxBoxItemLine::BOTTOM) + && nDist == rBox.GetDistance(SvxBoxItemLine::RIGHT)) + m_aSectionBreaks.append( + OutBorderLine(m_rExport, rBox.GetTop(), OOO_STRING_SVTOOLS_RTF_BOX, nDist)); + else + { + SvxShadowLocation eShadowLocation = SvxShadowLocation::NONE; + if (const SvxShadowItem* pItem = GetExport().HasItem(RES_SHADOW)) + eShadowLocation = pItem->GetLocation(); + + const SvxBoxItemLine* pBrd = aBorders; + const char** pBrdNms = aBorderNames; + for (int i = 0; i < 4; ++i, ++pBrd, ++pBrdNms) + { + editeng::SvxBorderLine const* const pLn = rBox.GetLine(*pBrd); + m_aSectionBreaks.append( + OutBorderLine(m_rExport, pLn, *pBrdNms, rBox.GetDistance(*pBrd), eShadowLocation)); + } + } + + if (!m_bBufferSectionBreaks) + m_aStyles.append(m_aSectionBreaks.makeStringAndClear()); +} + +void RtfAttributeOutput::FormatColumns_Impl(sal_uInt16 nCols, const SwFormatCol& rCol, bool bEven, + SwTwips nPageSize) +{ + m_rExport.Strm().WriteCharPtr(OOO_STRING_SVTOOLS_RTF_COLS); + m_rExport.OutLong(nCols); + + if (rCol.GetLineAdj() != COLADJ_NONE) + m_rExport.Strm().WriteCharPtr(OOO_STRING_SVTOOLS_RTF_LINEBETCOL); + + if (bEven) + { + m_rExport.Strm().WriteCharPtr(OOO_STRING_SVTOOLS_RTF_COLSX); + m_rExport.OutLong(rCol.GetGutterWidth(true)); + } + else + { + const SwColumns& rColumns = rCol.GetColumns(); + for (sal_uInt16 n = 0; n < nCols;) + { + m_rExport.Strm().WriteCharPtr(OOO_STRING_SVTOOLS_RTF_COLNO); + m_rExport.OutLong(n + 1); + + m_rExport.Strm().WriteCharPtr(OOO_STRING_SVTOOLS_RTF_COLW); + m_rExport.OutLong(rCol.CalcPrtColWidth(n, nPageSize)); + + if (++n != nCols) + { + m_rExport.Strm().WriteCharPtr(OOO_STRING_SVTOOLS_RTF_COLSR); + m_rExport.OutLong(rColumns[n - 1].GetRight() + rColumns[n].GetLeft()); + } + } + } +} + +void RtfAttributeOutput::FormatKeep(const SvxFormatKeepItem& rItem) +{ + if (rItem.GetValue()) + m_aStyles.append(OOO_STRING_SVTOOLS_RTF_KEEPN); +} + +void RtfAttributeOutput::FormatTextGrid(const SwTextGridItem& /*rGrid*/) +{ + SAL_INFO("sw.rtf", "TODO: " << __func__); +} + +void RtfAttributeOutput::FormatLineNumbering(const SwFormatLineNumber& rNumbering) +{ + if (!rNumbering.IsCount()) + m_aStyles.append(OOO_STRING_SVTOOLS_RTF_NOLINE); +} + +void RtfAttributeOutput::FormatFrameDirection(const SvxFrameDirectionItem& rDirection) +{ + SvxFrameDirection nDir = rDirection.GetValue(); + if (nDir == SvxFrameDirection::Environment) + nDir = GetExport().GetDefaultFrameDirection(); + + if (m_rExport.m_bOutPageDescs) + { + if (nDir == SvxFrameDirection::Vertical_RL_TB) + { + m_aSectionBreaks.append(OOO_STRING_SVTOOLS_RTF_STEXTFLOW); + m_aSectionBreaks.append(static_cast<sal_Int32>(1)); + if (!m_bBufferSectionBreaks) + m_rExport.Strm().WriteOString(m_aSectionBreaks.makeStringAndClear()); + } + return; + } + + if (m_rExport.GetRTFFlySyntax()) + { + if (nDir == SvxFrameDirection::Vertical_RL_TB) + { + // Top to bottom non-ASCII font + m_aFlyProperties.push_back(std::make_pair<OString, OString>("txflTextFlow", "3")); + } + else if (rDirection.GetValue() == SvxFrameDirection::Vertical_LR_BT) + { + // Bottom to top non-ASCII font + m_aFlyProperties.push_back(std::make_pair<OString, OString>("txflTextFlow", "2")); + } + return; + } + + if (nDir == SvxFrameDirection::Horizontal_RL_TB) + m_aStyles.append(OOO_STRING_SVTOOLS_RTF_RTLPAR); + else + m_aStyles.append(OOO_STRING_SVTOOLS_RTF_LTRPAR); +} + +void RtfAttributeOutput::ParaGrabBag(const SfxGrabBagItem& rItem) +{ + const std::map<OUString, css::uno::Any>& rMap = rItem.GetGrabBag(); + for (const auto& rValue : rMap) + { + if (rValue.first == "ParaTopMarginBeforeAutoSpacing") + { + m_bParaBeforeAutoSpacing = true; + rValue.second >>= m_nParaBeforeSpacing; + m_nParaBeforeSpacing = o3tl::toTwips(m_nParaBeforeSpacing, o3tl::Length::mm100); + } + else if (rValue.first == "ParaBottomMarginAfterAutoSpacing") + { + m_bParaAfterAutoSpacing = true; + rValue.second >>= m_nParaAfterSpacing; + m_nParaAfterSpacing = o3tl::toTwips(m_nParaAfterSpacing, o3tl::Length::mm100); + } + } +} + +void RtfAttributeOutput::CharGrabBag(const SfxGrabBagItem& /*rItem*/) {} + +void RtfAttributeOutput::ParaOutlineLevel(const SfxUInt16Item& /*rItem*/) {} + +void RtfAttributeOutput::WriteExpand(const SwField* pField) +{ + OUString sCmd; // for optional Parameters + switch (pField->GetTyp()->Which()) + { + //#i119803# Export user field for RTF filter + case SwFieldIds::User: + sCmd = pField->GetTyp()->GetName(); + m_rExport.OutputField(pField, ww::eNONE, sCmd); + break; + default: + m_rExport.OutputField(pField, ww::eUNKNOWN, sCmd); + break; + } +} + +void RtfAttributeOutput::RefField(const SwField& /*rField*/, const OUString& /*rRef*/) +{ + SAL_INFO("sw.rtf", "TODO: " << __func__); +} + +void RtfAttributeOutput::HiddenField(const SwField& /*rField*/) +{ + SAL_INFO("sw.rtf", "TODO: " << __func__); +} + +void RtfAttributeOutput::SetField(const SwField& /*rField*/, ww::eField /*eType*/, + const OUString& /*rCmd*/) +{ + SAL_INFO("sw.rtf", "TODO: " << __func__); +} + +void RtfAttributeOutput::PostitField(const SwField* pField) +{ + const SwPostItField& rPField = *static_cast<const SwPostItField*>(pField); + + OString aName = OUStringToOString(rPField.GetName(), RTL_TEXTENCODING_UTF8); + auto it = m_rOpenedAnnotationMarksIds.find(aName); + if (it != m_rOpenedAnnotationMarksIds.end()) + { + // In case this field is inside annotation marks, we want to write the + // annotation itself after the annotation mark is closed, not here. + m_aPostitFields[it->second] = &rPField; + return; + } + + m_aRunText->append("{" OOO_STRING_SVTOOLS_RTF_IGNORE OOO_STRING_SVTOOLS_RTF_ATNID " "); + m_aRunText->append(OUStringToOString(rPField.GetInitials(), m_rExport.GetCurrentEncoding())); + m_aRunText->append("}"); + m_aRunText->append("{" OOO_STRING_SVTOOLS_RTF_IGNORE OOO_STRING_SVTOOLS_RTF_ATNAUTHOR " "); + m_aRunText->append(OUStringToOString(rPField.GetPar1(), m_rExport.GetCurrentEncoding())); + m_aRunText->append("}"); + m_aRunText->append(OOO_STRING_SVTOOLS_RTF_CHATN); + + m_aRunText->append("{" OOO_STRING_SVTOOLS_RTF_IGNORE OOO_STRING_SVTOOLS_RTF_ANNOTATION); + + if (m_nCurrentAnnotationMarkId != -1) + { + m_aRunText->append("{" OOO_STRING_SVTOOLS_RTF_IGNORE OOO_STRING_SVTOOLS_RTF_ATNREF " "); + m_aRunText->append(m_nCurrentAnnotationMarkId); + m_aRunText->append('}'); + } + m_aRunText->append("{" OOO_STRING_SVTOOLS_RTF_IGNORE OOO_STRING_SVTOOLS_RTF_ATNDATE " "); + m_aRunText->append(static_cast<sal_Int32>(sw::ms::DateTime2DTTM(rPField.GetDateTime()))); + m_aRunText->append('}'); + if (const OutlinerParaObject* pObject = rPField.GetTextObject()) + m_rExport.SdrExporter().WriteOutliner(*pObject, TXT_ATN); + m_aRunText->append('}'); +} + +bool RtfAttributeOutput::DropdownField(const SwField* /*pField*/) +{ + // this is handled in OutputFlyFrame_Impl() + return true; +} + +bool RtfAttributeOutput::PlaceholderField(const SwField* pField) +{ + m_aRunText->append("{" OOO_STRING_SVTOOLS_RTF_FIELD + "{" OOO_STRING_SVTOOLS_RTF_IGNORE OOO_STRING_SVTOOLS_RTF_FLDINST + " MACROBUTTON None "); + RunText(pField->GetPar1()); + m_aRunText->append("}}"); + return false; // do not expand +} + +RtfAttributeOutput::RtfAttributeOutput(RtfExport& rExport) + : AttributeOutputBase("") // ConvertURL isn't used now in RTF output + , m_rExport(rExport) + , m_pPrevPageDesc(nullptr) + , m_nStyleId(0) + , m_nListId(0) + , m_bIsRTL(false) + , m_nScript(i18n::ScriptType::LATIN) + , m_bControlLtrRtl(false) + , m_nNextAnnotationMarkId(0) + , m_nCurrentAnnotationMarkId(-1) + , m_bTableCellOpen(false) + , m_nTableDepth(0) + , m_bTableAfterCell(false) + , m_nColBreakNeeded(false) + , m_bBufferSectionBreaks(false) + , m_bBufferSectionHeaders(false) + , m_bLastTable(true) + , m_bWroteCellInfo(false) + , m_bTableRowEnded(false) + , m_bIsBeforeFirstParagraph(true) + , m_bSingleEmptyRun(false) + , m_bInRun(false) + , m_bInRuby(false) + , m_pFlyFrameSize(nullptr) + , m_bParaBeforeAutoSpacing(false) + , m_nParaBeforeSpacing(0) + , m_bParaAfterAutoSpacing(false) + , m_nParaAfterSpacing(0) +{ +} + +RtfAttributeOutput::~RtfAttributeOutput() = default; + +MSWordExportBase& RtfAttributeOutput::GetExport() { return m_rExport; } + +// These are used by wwFont::WriteRtf() + +/// Start the font. +void RtfAttributeOutput::StartFont(const OUString& rFamilyName) const +{ + // write the font name hex-encoded, but without Unicode - Word at least + // cannot read *both* Unicode and fallback as written by OutString + m_rExport.Strm().WriteCharPtr( + msfilter::rtfutil::OutString(rFamilyName, m_rExport.GetCurrentEncoding(), false).getStr()); +} + +/// End the font. +void RtfAttributeOutput::EndFont() const +{ + m_rExport.Strm().WriteCharPtr(";}"); + m_rExport.SetCurrentEncoding(m_rExport.GetDefaultEncoding()); +} + +/// Alternate name for the font. +void RtfAttributeOutput::FontAlternateName(const OUString& rName) const +{ + m_rExport.Strm() + .WriteChar('{') + .WriteCharPtr(OOO_STRING_SVTOOLS_RTF_IGNORE) + .WriteCharPtr(OOO_STRING_SVTOOLS_RTF_FALT) + .WriteChar(' '); + // write the font name hex-encoded, but without Unicode - Word at least + // cannot read *both* Unicode and fallback as written by OutString + m_rExport.Strm() + .WriteCharPtr( + msfilter::rtfutil::OutString(rName, m_rExport.GetCurrentEncoding(), false).getStr()) + .WriteChar('}'); +} + +/// Font charset. +void RtfAttributeOutput::FontCharset(sal_uInt8 nCharSet) const +{ + m_rExport.Strm().WriteCharPtr(OOO_STRING_SVTOOLS_RTF_FCHARSET); + m_rExport.OutULong(nCharSet); + m_rExport.Strm().WriteChar(' '); + m_rExport.SetCurrentEncoding(rtl_getTextEncodingFromWindowsCharset(nCharSet)); +} + +/// Font family. +void RtfAttributeOutput::FontFamilyType(FontFamily eFamily, const wwFont& rFont) const +{ + m_rExport.Strm().WriteChar('{').WriteCharPtr(OOO_STRING_SVTOOLS_RTF_F); + + const char* pStr = OOO_STRING_SVTOOLS_RTF_FNIL; + switch (eFamily) + { + case FAMILY_ROMAN: + pStr = OOO_STRING_SVTOOLS_RTF_FROMAN; + break; + case FAMILY_SWISS: + pStr = OOO_STRING_SVTOOLS_RTF_FSWISS; + break; + case FAMILY_MODERN: + pStr = OOO_STRING_SVTOOLS_RTF_FMODERN; + break; + case FAMILY_SCRIPT: + pStr = OOO_STRING_SVTOOLS_RTF_FSCRIPT; + break; + case FAMILY_DECORATIVE: + pStr = OOO_STRING_SVTOOLS_RTF_FDECOR; + break; + default: + break; + } + m_rExport.OutULong(m_rExport.m_aFontHelper.GetId(rFont)).WriteCharPtr(pStr); +} + +/// Font pitch. +void RtfAttributeOutput::FontPitchType(FontPitch ePitch) const +{ + m_rExport.Strm().WriteCharPtr(OOO_STRING_SVTOOLS_RTF_FPRQ); + + sal_uInt16 nVal = 0; + switch (ePitch) + { + case PITCH_FIXED: + nVal = 1; + break; + case PITCH_VARIABLE: + nVal = 2; + break; + default: + break; + } + m_rExport.OutULong(nVal); +} + +static void lcl_AppendSP(OStringBuffer& rBuffer, const char cName[], const OUString& rValue, + const RtfExport& rExport) +{ + rBuffer.append("{" OOO_STRING_SVTOOLS_RTF_SP "{"); // "{\sp{" + rBuffer.append(OOO_STRING_SVTOOLS_RTF_SN " "); //" \sn " + rBuffer.append(cName); //"PropName" + rBuffer.append("}{" OOO_STRING_SVTOOLS_RTF_SV " "); + // "}{ \sv " + rBuffer.append(msfilter::rtfutil::OutString(rValue, rExport.GetCurrentEncoding())); + rBuffer.append("}}"); +} + +static OString ExportPICT(const SwFlyFrameFormat* pFlyFrameFormat, const Size& rOrig, + const Size& rRendered, const Size& rMapped, const SwCropGrf& rCr, + const char* pBLIPType, const sal_uInt8* pGraphicAry, sal_uInt64 nSize, + const RtfExport& rExport, SvStream* pStream = nullptr, + bool bWritePicProp = true, const SwAttrSet* pAttrSet = nullptr) +{ + OStringBuffer aRet; + if (pBLIPType && nSize && pGraphicAry) + { + bool bIsWMF = std::strcmp(pBLIPType, OOO_STRING_SVTOOLS_RTF_WMETAFILE) == 0; + + aRet.append("{" OOO_STRING_SVTOOLS_RTF_PICT); + + if (pFlyFrameFormat && bWritePicProp) + { + OUString sDescription = pFlyFrameFormat->GetObjDescription(); + //write picture properties - wzDescription at first + //looks like: "{\*\picprop{\sp{\sn PropertyName}{\sv PropertyValue}}}" + aRet.append( + "{" OOO_STRING_SVTOOLS_RTF_IGNORE OOO_STRING_SVTOOLS_RTF_PICPROP); //"{\*\picprop + lcl_AppendSP(aRet, "wzDescription", sDescription, rExport); + OUString sName = pFlyFrameFormat->GetObjTitle(); + lcl_AppendSP(aRet, "wzName", sName, rExport); + + if (pAttrSet) + { + MirrorGraph eMirror = pAttrSet->Get(RES_GRFATR_MIRRORGRF).GetValue(); + if (eMirror == MirrorGraph::Vertical || eMirror == MirrorGraph::Both) + // Mirror on the vertical axis is a horizontal flip. + lcl_AppendSP(aRet, "fFlipH", "1", rExport); + } + + aRet.append("}"); //"}" + } + + tools::Long nXCroppedSize = rOrig.Width() - (rCr.GetLeft() + rCr.GetRight()); + tools::Long nYCroppedSize = rOrig.Height() - (rCr.GetTop() + rCr.GetBottom()); + /* Graphic with a zero height or width, typically copied from webpages, caused crashes. */ + if (!nXCroppedSize) + nXCroppedSize = 100; + if (!nYCroppedSize) + nYCroppedSize = 100; + + //Given the original size and taking cropping into account + //first, how much has the original been scaled to get the + //final rendered size + aRet.append(OOO_STRING_SVTOOLS_RTF_PICSCALEX); + aRet.append(static_cast<sal_Int32>((100 * rRendered.Width()) / nXCroppedSize)); + aRet.append(OOO_STRING_SVTOOLS_RTF_PICSCALEY); + aRet.append(static_cast<sal_Int32>((100 * rRendered.Height()) / nYCroppedSize)); + + aRet.append(OOO_STRING_SVTOOLS_RTF_PICCROPL); + aRet.append(rCr.GetLeft()); + aRet.append(OOO_STRING_SVTOOLS_RTF_PICCROPR); + aRet.append(rCr.GetRight()); + aRet.append(OOO_STRING_SVTOOLS_RTF_PICCROPT); + aRet.append(rCr.GetTop()); + aRet.append(OOO_STRING_SVTOOLS_RTF_PICCROPB); + aRet.append(rCr.GetBottom()); + + aRet.append(OOO_STRING_SVTOOLS_RTF_PICW); + aRet.append(static_cast<sal_Int32>(rMapped.Width())); + aRet.append(OOO_STRING_SVTOOLS_RTF_PICH); + aRet.append(static_cast<sal_Int32>(rMapped.Height())); + + aRet.append(OOO_STRING_SVTOOLS_RTF_PICWGOAL); + aRet.append(static_cast<sal_Int32>(rOrig.Width())); + aRet.append(OOO_STRING_SVTOOLS_RTF_PICHGOAL); + aRet.append(static_cast<sal_Int32>(rOrig.Height())); + + aRet.append(pBLIPType); + if (bIsWMF) + { + aRet.append(sal_Int32(8)); + msfilter::rtfutil::StripMetafileHeader(pGraphicAry, nSize); + } + aRet.append(SAL_NEWLINE_STRING); + if (pStream) + pStream->WriteOString(aRet.makeStringAndClear()); + if (pStream) + msfilter::rtfutil::WriteHex(pGraphicAry, nSize, pStream); + else + aRet.append(msfilter::rtfutil::WriteHex(pGraphicAry, nSize)); + aRet.append('}'); + if (pStream) + pStream->WriteOString(aRet.makeStringAndClear()); + } + return aRet.makeStringAndClear(); +} + +void RtfAttributeOutput::FlyFrameOLEReplacement(const SwFlyFrameFormat* pFlyFrameFormat, + SwOLENode& rOLENode, const Size& rSize) +{ + m_aRunText->append("{" OOO_STRING_SVTOOLS_RTF_IGNORE OOO_STRING_SVTOOLS_RTF_SHPPICT); + Size aSize(rOLENode.GetTwipSize()); + Size aRendered(aSize); + aRendered.setWidth(rSize.Width()); + aRendered.setHeight(rSize.Height()); + const Graphic* pGraphic = rOLENode.GetGraphic(); + Size aMapped(pGraphic->GetPrefSize()); + auto& rCr = rOLENode.GetAttr(RES_GRFATR_CROPGRF); + const char* pBLIPType = OOO_STRING_SVTOOLS_RTF_PNGBLIP; + const sal_uInt8* pGraphicAry = nullptr; + SvMemoryStream aStream; + if (GraphicConverter::Export(aStream, *pGraphic, ConvertDataFormat::PNG) != ERRCODE_NONE) + SAL_WARN("sw.rtf", "failed to export the graphic"); + sal_uInt32 nSize = aStream.TellEnd(); + pGraphicAry = static_cast<sal_uInt8 const*>(aStream.GetData()); + m_aRunText->append(ExportPICT(pFlyFrameFormat, aSize, aRendered, aMapped, rCr, pBLIPType, + pGraphicAry, nSize, m_rExport)); + m_aRunText->append("}"); // shppict + m_aRunText->append("{" OOO_STRING_SVTOOLS_RTF_NONSHPPICT); + pBLIPType = OOO_STRING_SVTOOLS_RTF_WMETAFILE; + SvMemoryStream aWmfStream; + if (GraphicConverter::Export(aWmfStream, *pGraphic, ConvertDataFormat::WMF) != ERRCODE_NONE) + SAL_WARN("sw.rtf", "failed to export the graphic"); + nSize = aWmfStream.TellEnd(); + pGraphicAry = static_cast<sal_uInt8 const*>(aWmfStream.GetData()); + m_aRunText->append(ExportPICT(pFlyFrameFormat, aSize, aRendered, aMapped, rCr, pBLIPType, + pGraphicAry, nSize, m_rExport)); + m_aRunText->append("}"); // nonshppict +} + +bool RtfAttributeOutput::FlyFrameOLEMath(const SwFlyFrameFormat* pFlyFrameFormat, + SwOLENode& rOLENode, const Size& rSize) +{ + uno::Reference<embed::XEmbeddedObject> xObj(rOLENode.GetOLEObj().GetOleRef()); + sal_Int64 nAspect = rOLENode.GetAspect(); + svt::EmbeddedObjectRef aObjRef(xObj, nAspect); + SvGlobalName aObjName(aObjRef->getClassID()); + + if (!SotExchange::IsMath(aObjName)) + return false; + + m_aRunText->append("{" LO_STRING_SVTOOLS_RTF_MMATH " "); + uno::Reference<util::XCloseable> xClosable = xObj->getComponent(); + if (!xClosable.is()) + return false; + // 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 pBase + = dynamic_cast<oox::FormulaExportBase*>(dynamic_cast<SfxBaseModel*>(xClosable.get())); + assert(pBase != nullptr); + OStringBuffer aBuf; + if (pBase) + pBase->writeFormulaRtf(aBuf, m_rExport.GetCurrentEncoding()); + m_aRunText->append(aBuf); + // Replacement graphic. + m_aRunText->append("{" LO_STRING_SVTOOLS_RTF_MMATHPICT " "); + FlyFrameOLEReplacement(pFlyFrameFormat, rOLENode, rSize); + m_aRunText->append("}"); // mmathPict + m_aRunText->append("}"); // mmath + + return true; +} + +void RtfAttributeOutput::FlyFrameOLE(const SwFlyFrameFormat* pFlyFrameFormat, SwOLENode& rOLENode, + const Size& rSize) +{ + if (FlyFrameOLEMath(pFlyFrameFormat, rOLENode, rSize)) + return; + + FlyFrameOLEReplacement(pFlyFrameFormat, rOLENode, rSize); +} + +void RtfAttributeOutput::FlyFrameGraphic(const SwFlyFrameFormat* pFlyFrameFormat, + const SwGrfNode* pGrfNode) +{ + SvMemoryStream aStream; + const sal_uInt8* pGraphicAry = nullptr; + sal_uInt32 nSize = 0; + + const Graphic& rGraphic(pGrfNode->GetGrf()); + + // If there is no graphic there is not much point in parsing it + if (rGraphic.GetType() == GraphicType::NONE) + return; + + ConvertDataFormat aConvertDestinationFormat = ConvertDataFormat::WMF; + const char* pConvertDestinationBLIPType = OOO_STRING_SVTOOLS_RTF_WMETAFILE; + + GfxLink aGraphicLink; + const char* pBLIPType = nullptr; + if (rGraphic.IsGfxLink()) + { + aGraphicLink = rGraphic.GetGfxLink(); + nSize = aGraphicLink.GetDataSize(); + pGraphicAry = aGraphicLink.GetData(); + switch (aGraphicLink.GetType()) + { + // #i15508# trying to add BMP type for better exports, need to check if this works + // checked, does not work. Also need to reset pGraphicAry to NULL to force conversion + // to PNG, else the BMP array will be used. + // It may work using direct DIB data, but that needs to be checked eventually + // + // #i15508# before GfxLinkType::NativeBmp was added the graphic data + // (to be hold in pGraphicAry) was not available; thus for now to stay + // compatible, keep it that way by assigning NULL value to pGraphicAry + case GfxLinkType::NativeBmp: + // pBLIPType = OOO_STRING_SVTOOLS_RTF_WBITMAP; + pGraphicAry = nullptr; + break; + + case GfxLinkType::NativeJpg: + pBLIPType = OOO_STRING_SVTOOLS_RTF_JPEGBLIP; + break; + case GfxLinkType::NativePng: + pBLIPType = OOO_STRING_SVTOOLS_RTF_PNGBLIP; + break; + case GfxLinkType::NativeWmf: + pBLIPType = aGraphicLink.IsEMF() ? OOO_STRING_SVTOOLS_RTF_EMFBLIP + : OOO_STRING_SVTOOLS_RTF_WMETAFILE; + break; + case GfxLinkType::NativeGif: + // GIF is not supported by RTF, but we override default conversion to WMF, PNG seems fits better here. + aConvertDestinationFormat = ConvertDataFormat::PNG; + pConvertDestinationBLIPType = OOO_STRING_SVTOOLS_RTF_PNGBLIP; + break; + default: + break; + } + } + + GraphicType eGraphicType = rGraphic.GetType(); + if (!pGraphicAry) + { + if (ERRCODE_NONE + == GraphicConverter::Export(aStream, rGraphic, + (eGraphicType == GraphicType::Bitmap) + ? ConvertDataFormat::PNG + : ConvertDataFormat::WMF)) + { + pBLIPType = (eGraphicType == GraphicType::Bitmap) ? OOO_STRING_SVTOOLS_RTF_PNGBLIP + : OOO_STRING_SVTOOLS_RTF_WMETAFILE; + nSize = aStream.TellEnd(); + pGraphicAry = static_cast<sal_uInt8 const*>(aStream.GetData()); + } + } + + Size aMapped(eGraphicType == GraphicType::Bitmap ? rGraphic.GetSizePixel() + : rGraphic.GetPrefSize()); + + auto& rCr = pGrfNode->GetAttr(RES_GRFATR_CROPGRF); + + //Get original size in twips + Size aSize(pGrfNode->GetTwipSize()); + Size aRendered(aSize); + + const SwFormatFrameSize& rS = pFlyFrameFormat->GetFrameSize(); + aRendered.setWidth(rS.GetWidth()); + aRendered.setHeight(rS.GetHeight()); + + ww8::Frame* pFrame = nullptr; + for (auto& rFrame : m_rExport.m_aFrames) + { + if (pFlyFrameFormat == &rFrame.GetFrameFormat()) + { + pFrame = &rFrame; + break; + } + } + + /* + If the graphic is not of type WMF then we will have to store two + graphics, one in the native format wrapped in shppict, and the other in + the wmf format wrapped in nonshppict, so as to keep wordpad happy. If it's + a wmf already then we don't need any such wrapping + */ + bool bIsWMF = pBLIPType && std::strcmp(pBLIPType, OOO_STRING_SVTOOLS_RTF_WMETAFILE) == 0; + const SwAttrSet* pAttrSet = pGrfNode->GetpSwAttrSet(); + if (!pFrame || pFrame->IsInline()) + { + if (!bIsWMF) + m_rExport.Strm().WriteCharPtr( + "{" OOO_STRING_SVTOOLS_RTF_IGNORE OOO_STRING_SVTOOLS_RTF_SHPPICT); + } + else + { + m_rExport.Strm().WriteCharPtr( + "{" OOO_STRING_SVTOOLS_RTF_SHP + "{" OOO_STRING_SVTOOLS_RTF_IGNORE OOO_STRING_SVTOOLS_RTF_SHPINST); + m_pFlyFrameSize = &aRendered; + m_rExport.m_pParentFrame = pFrame; + m_rExport.m_bOutFlyFrameAttrs = true; + m_rExport.SetRTFFlySyntax(true); + m_rExport.OutputFormat(pFrame->GetFrameFormat(), false, false, true); + m_rExport.m_bOutFlyFrameAttrs = false; + m_rExport.SetRTFFlySyntax(false); + m_rExport.m_pParentFrame = nullptr; + m_pFlyFrameSize = nullptr; + std::vector<std::pair<OString, OString>> aFlyProperties{ + { "shapeType", OString::number(ESCHER_ShpInst_PictureFrame) }, + + { "wzDescription", msfilter::rtfutil::OutString(pFlyFrameFormat->GetObjDescription(), + m_rExport.GetCurrentEncoding()) }, + { "wzName", msfilter::rtfutil::OutString(pFlyFrameFormat->GetObjTitle(), + m_rExport.GetCurrentEncoding()) } + }; + + // If we have a wrap polygon, then handle that here. + if (pFlyFrameFormat->GetSurround().IsContour()) + { + if (const SwNoTextNode* pNd + = sw::util::GetNoTextNodeFromSwFrameFormat(*pFlyFrameFormat)) + { + const tools::PolyPolygon* pPolyPoly = pNd->HasContour(); + if (pPolyPoly && pPolyPoly->Count()) + { + tools::Polygon aPoly = sw::util::CorrectWordWrapPolygonForExport( + *pPolyPoly, pNd, /*bCorrectCrop=*/true); + OStringBuffer aVerticies; + for (sal_uInt16 i = 0; i < aPoly.GetSize(); ++i) + aVerticies.append(";(" + OString::number(aPoly[i].X()) + "," + + OString::number(aPoly[i].Y()) + ")"); + aFlyProperties.push_back(std::make_pair<OString, OString>( + "pWrapPolygonVertices", + "8;" + OString::number(aPoly.GetSize()) + aVerticies.makeStringAndClear())); + } + } + } + + // Below text, behind document, opaque: they all refer to the same thing. + if (!pFlyFrameFormat->GetOpaque().GetValue()) + aFlyProperties.push_back(std::make_pair<OString, OString>("fBehindDocument", "1")); + + if (pAttrSet) + { + if (Degree10 nRot10 = pAttrSet->Get(RES_GRFATR_ROTATION).GetValue()) + { + // See writerfilter::rtftok::RTFSdrImport::applyProperty(), + // positive rotation angles are clockwise in RTF, we have them + // as counter-clockwise. + // Additionally, RTF type is 0..360*2^16, our is 0..360*10. + sal_Int32 nRot = nRot10.get() * -1 * RTF_MULTIPLIER / 10; + aFlyProperties.emplace_back("rotation", OString::number(nRot)); + } + } + + for (const std::pair<OString, OString>& rPair : aFlyProperties) + { + m_rExport.Strm().WriteCharPtr("{" OOO_STRING_SVTOOLS_RTF_SP "{"); + m_rExport.Strm().WriteCharPtr(OOO_STRING_SVTOOLS_RTF_SN " "); + m_rExport.Strm().WriteOString(rPair.first); + m_rExport.Strm().WriteCharPtr("}{" OOO_STRING_SVTOOLS_RTF_SV " "); + m_rExport.Strm().WriteOString(rPair.second); + m_rExport.Strm().WriteCharPtr("}}"); + } + m_rExport.Strm().WriteCharPtr("{" OOO_STRING_SVTOOLS_RTF_SP "{" OOO_STRING_SVTOOLS_RTF_SN + " pib" + "}{" OOO_STRING_SVTOOLS_RTF_SV " "); + } + + bool bWritePicProp = !pFrame || pFrame->IsInline(); + if (pBLIPType) + ExportPICT(pFlyFrameFormat, aSize, aRendered, aMapped, rCr, pBLIPType, pGraphicAry, nSize, + m_rExport, &m_rExport.Strm(), bWritePicProp, pAttrSet); + else + { + aStream.Seek(0); + if (GraphicConverter::Export(aStream, rGraphic, aConvertDestinationFormat) != ERRCODE_NONE) + SAL_WARN("sw.rtf", "failed to export the graphic"); + pBLIPType = pConvertDestinationBLIPType; + nSize = aStream.TellEnd(); + pGraphicAry = static_cast<sal_uInt8 const*>(aStream.GetData()); + + ExportPICT(pFlyFrameFormat, aSize, aRendered, aMapped, rCr, pBLIPType, pGraphicAry, nSize, + m_rExport, &m_rExport.Strm(), bWritePicProp, pAttrSet); + } + + if (!pFrame || pFrame->IsInline()) + { + if (!bIsWMF) + { + m_rExport.Strm().WriteCharPtr("}" + "{" OOO_STRING_SVTOOLS_RTF_NONSHPPICT); + + aStream.Seek(0); + if (GraphicConverter::Export(aStream, rGraphic, ConvertDataFormat::WMF) != ERRCODE_NONE) + SAL_WARN("sw.rtf", "failed to export the graphic"); + pBLIPType = OOO_STRING_SVTOOLS_RTF_WMETAFILE; + nSize = aStream.TellEnd(); + pGraphicAry = static_cast<sal_uInt8 const*>(aStream.GetData()); + + ExportPICT(pFlyFrameFormat, aSize, aRendered, aMapped, rCr, pBLIPType, pGraphicAry, + nSize, m_rExport, &m_rExport.Strm()); + + m_rExport.Strm().WriteChar('}'); + } + } + else + m_rExport.Strm().WriteCharPtr("}}}}"); // Close SV, SP, SHPINST and SHP. + + m_rExport.Strm().WriteCharPtr(SAL_NEWLINE_STRING); +} + +void RtfAttributeOutput::BulletDefinition(int /*nId*/, const Graphic& rGraphic, Size aSize) +{ + m_rExport.Strm().WriteCharPtr("{" OOO_STRING_SVTOOLS_RTF_IGNORE OOO_STRING_SVTOOLS_RTF_SHPPICT); + m_rExport.Strm().WriteCharPtr("{" OOO_STRING_SVTOOLS_RTF_PICT OOO_STRING_SVTOOLS_RTF_PNGBLIP); + + m_rExport.Strm().WriteCharPtr(OOO_STRING_SVTOOLS_RTF_PICWGOAL); + m_rExport.OutULong(aSize.Width()); + m_rExport.Strm().WriteCharPtr(OOO_STRING_SVTOOLS_RTF_PICHGOAL); + m_rExport.OutULong(aSize.Height()); + + m_rExport.Strm().WriteCharPtr(SAL_NEWLINE_STRING); + const sal_uInt8* pGraphicAry = nullptr; + SvMemoryStream aStream; + if (GraphicConverter::Export(aStream, rGraphic, ConvertDataFormat::PNG) != ERRCODE_NONE) + SAL_WARN("sw.rtf", "failed to export the numbering picture bullet"); + sal_uInt32 nSize = aStream.TellEnd(); + pGraphicAry = static_cast<sal_uInt8 const*>(aStream.GetData()); + msfilter::rtfutil::WriteHex(pGraphicAry, nSize, &m_rExport.Strm()); + m_rExport.Strm().WriteCharPtr("}}"); // pict, shppict +} + +void RtfAttributeOutput::SectionRtlGutter(const SfxBoolItem& rRtlGutter) +{ + if (!rRtlGutter.GetValue()) + { + return; + } + + m_rExport.Strm().WriteCharPtr(LO_STRING_SVTOOLS_RTF_RTLGUTTER); +} + +void RtfAttributeOutput::TextLineBreak(const SwFormatLineBreak& rLineBreak) +{ + // Text wrapping break of type: + m_aStyles.append(LO_STRING_SVTOOLS_RTF_LBR); + m_aStyles.append(static_cast<sal_Int32>(rLineBreak.GetEnumValue())); + + // Write the linebreak itself. + RunText("\x0b"); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ |