/* -*- 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 <sal/config.h>
 
#include "rtfexport.hxx"
 
#include <string_view>
 
#include "rtfexportfilter.hxx"
#include "rtfsdrexport.hxx"
#include "rtfattributeoutput.hxx"
#include <com/sun/star/document/XDocumentPropertiesSupplier.hpp>
#include <com/sun/star/frame/XModel.hpp>
#include <com/sun/star/i18n/ScriptType.hpp>
#include <com/sun/star/beans/XPropertySet.hpp>
#include <com/sun/star/beans/XPropertySetInfo.hpp>
#include <com/sun/star/text/XTextFieldsSupplier.hpp>
#include <docsh.hxx>
#include <viewsh.hxx>
#include <viewopt.hxx>
#include <fmtpdsc.hxx>
#include <ftninfo.hxx>
#include <fmthdft.hxx>
#include <editeng/colritem.hxx>
#include <editeng/udlnitem.hxx>
#include <editeng/fontitem.hxx>
#include <editeng/paperinf.hxx>
#include <editeng/brushitem.hxx>
#include <editeng/protitem.hxx>
#include <editeng/lrspitem.hxx>
#include <editeng/ulspitem.hxx>
#include <editeng/boxitem.hxx>
#include <editeng/shaditem.hxx>
#include <lineinfo.hxx>
#include <redline.hxx>
#include <swmodule.hxx>
#include <IDocumentLayoutAccess.hxx>
#include <comphelper/string.hxx>
#include <svtools/rtfkeywd.hxx>
#include <filter/msfilter/rtfutil.hxx>
#include <unotools/docinfohelper.hxx>
#include <unotools/securityoptions.hxx>
#include <xmloff/odffields.hxx>
#include <o3tl/string_view.hxx>
#include <osl/diagnose.h>
#include <rtl/tencinfo.h>
#include <sal/log.hxx>
#include <svx/xflclit.hxx>
#include <fmtmeta.hxx>
#include <IDocumentSettingAccess.hxx>
#include <fmtfsize.hxx>
#include <ndtxt.hxx>
#include <numrule.hxx>
#include <frmatr.hxx>
#include <swtable.hxx>
#include <IMark.hxx>
#include <fmtftntx.hxx>
#include <ftnidx.hxx>
#include <txtftn.hxx>
 
using namespace ::com::sun::star;
 
// the default text encoding for the export, if it doesn't fit unicode will
// be used
#define DEF_ENCODING RTL_TEXTENCODING_ASCII_US
 
AttributeOutputBase& RtfExport::AttrOutput() const { return *m_pAttrOutput; }
 
MSWordSections& RtfExport::Sections() const { return *m_pSections; }
 
RtfSdrExport& RtfExport::SdrExporter() const { return *m_pSdrExport; }
 
bool RtfExport::CollapseScriptsforWordOk(sal_uInt16 nScript, sal_uInt16 nWhich)
{
    // FIXME is this actually true for rtf? - this is copied from DOCX
    if (nScript == i18n::ScriptType::ASIAN)
    {
        // for asian in ww8, there is only one fontsize
        // and one fontstyle (posture/weight)
        switch (nWhich)
        {
            case RES_CHRATR_FONTSIZE:
            case RES_CHRATR_POSTURE:
            case RES_CHRATR_WEIGHT:
                return false;
            default:
                break;
        }
    }
    else if (nScript != i18n::ScriptType::COMPLEX)
    {
        // for western in ww8, there is only one fontsize
        // and one fontstyle (posture/weight)
        switch (nWhich)
        {
            case RES_CHRATR_CJK_FONTSIZE:
            case RES_CHRATR_CJK_POSTURE:
            case RES_CHRATR_CJK_WEIGHT:
                return false;
            default:
                break;
        }
    }
    return true;
}
 
void RtfExport::AppendBookmarks(const SwTextNode& rNode, sal_Int32 nCurrentPos, sal_Int32 nLen,
                                const SwRedlineData* /*pRedlineData*/)
{
    std::vector<OUString> aStarts;
    std::vector<OUString> aEnds;
 
    IMarkVector aMarks;
    if (GetBookmarks(rNode, nCurrentPos, nCurrentPos + nLen, aMarks))
    {
        for (const auto& pMark : aMarks)
        {
            const sal_Int32 nStart = pMark->GetMarkStart().GetContentIndex();
            const sal_Int32 nEnd = pMark->GetMarkEnd().GetContentIndex();
 
            if (nStart == nCurrentPos)
                aStarts.push_back(pMark->GetName());
 
            if (nEnd == nCurrentPos)
                aEnds.push_back(pMark->GetName());
        }
    }
 
    m_pAttrOutput->WriteBookmarks_Impl(aStarts, aEnds);
}
 
void RtfExport::AppendBookmark(const OUString& rName)
{
    std::vector<OUString> aStarts{ rName };
    std::vector<OUString> aEnds{ rName };
 
    m_pAttrOutput->WriteBookmarks_Impl(aStarts, aEnds);
}
 
void RtfExport::AppendAnnotationMarks(const SwWW8AttrIter& rAttrs, sal_Int32 nCurrentPos,
                                      sal_Int32 nLen)
{
    std::vector<OUString> aStarts;
    std::vector<OUString> aEnds;
 
    IMarkVector aMarks;
    if (GetAnnotationMarks(rAttrs, nCurrentPos, nCurrentPos + nLen, aMarks))
    {
        for (const auto& pMark : aMarks)
        {
            const sal_Int32 nStart = pMark->GetMarkStart().GetContentIndex();
            const sal_Int32 nEnd = pMark->GetMarkEnd().GetContentIndex();
 
            if (nStart == nCurrentPos)
                aStarts.push_back(pMark->GetName());
 
            if (nEnd == nCurrentPos)
                aEnds.push_back(pMark->GetName());
        }
    }
 
    m_pAttrOutput->WriteAnnotationMarks_Impl(aStarts, aEnds);
}
 
//For i120928,to export graphic of bullet for RTF filter
void RtfExport::ExportGrfBullet(const SwTextNode& /*rNd*/)
{
    // Noop, would be too late, see WriteNumbering() instead.
}
 
void RtfExport::WriteChar(sal_Unicode /*c*/) { /* WriteChar() has nothing to do for rtf. */}
 
static bool IsExportNumRule(const SwNumRule& rRule)
{
    sal_uInt8 nEnd = MAXLEVEL;
    while (nEnd-- && !rRule.GetNumFormat(nEnd))
        ;
    ++nEnd;
 
    sal_uInt8 nLvl;
 
    for (nLvl = 0; nLvl < nEnd; ++nLvl)
    {
        const SwNumFormat* pNFormat = &rRule.Get(nLvl);
        if (SVX_NUM_NUMBER_NONE != pNFormat->GetNumberingType() || !pNFormat->GetPrefix().isEmpty()
            || (!pNFormat->GetSuffix().isEmpty() && pNFormat->GetSuffix() != "."))
            break;
    }
 
    return nLvl != nEnd;
}
 
void RtfExport::BuildNumbering()
{
    const SwNumRuleTable& rListTable = m_rDoc.GetNumRuleTable();
 
    SwNumRule* pOutlineRule = m_rDoc.GetOutlineNumRule();
    if (IsExportNumRule(*pOutlineRule))
        GetNumberingId(*pOutlineRule);
 
    for (auto n = rListTable.size(); n;)
    {
        SwNumRule* pRule = rListTable[--n];
        if (!m_rDoc.IsUsed(*pRule))
            continue;
 
        if (IsExportNumRule(*pRule))
            GetNumberingId(*pRule);
    }
}
 
void RtfExport::WriteNumbering()
{
    SAL_INFO("sw.rtf", __func__ << " start");
 
    if (!m_pUsedNumTable)
        return; // no numbering is used
 
    Strm()
        .WriteChar('{')
        .WriteOString(OOO_STRING_SVTOOLS_RTF_IGNORE)
        .WriteOString(OOO_STRING_SVTOOLS_RTF_LISTTABLE);
 
    CollectGrfsOfBullets();
    if (!m_vecBulletPic.empty())
        Strm()
            .WriteChar('{')
            .WriteOString(OOO_STRING_SVTOOLS_RTF_IGNORE)
            .WriteOString(LO_STRING_SVTOOLS_RTF_LISTPICTURE);
    BulletDefinitions();
    if (!m_vecBulletPic.empty())
        Strm().WriteChar('}');
 
    AbstractNumberingDefinitions();
    Strm().WriteChar('}');
 
    Strm().WriteChar('{').WriteOString(OOO_STRING_SVTOOLS_RTF_LISTOVERRIDETABLE);
    NumberingDefinitions();
    Strm().WriteChar('}');
 
    SAL_INFO("sw.rtf", __func__ << " end");
}
 
void RtfExport::WriteRevTab()
{
    int nRevAuthors = m_rDoc.getIDocumentRedlineAccess().GetRedlineTable().size();
 
    if (nRevAuthors < 1)
        return;
 
    // RTF always seems to use Unknown as the default first entry
    GetRedline(u"Unknown"_ustr);
 
    for (SwRangeRedline* pRedl : m_rDoc.getIDocumentRedlineAccess().GetRedlineTable())
    {
        GetRedline(SW_MOD()->GetRedlineAuthor(pRedl->GetAuthor()));
    }
 
    bool bRemoveChangesInfo
        = SvtSecurityOptions::IsOptionSet(SvtSecurityOptions::EOption::DocWarnRemovePersonalInfo)
          && !SvtSecurityOptions::IsOptionSet(SvtSecurityOptions::EOption::DocWarnKeepRedlineInfo);
 
    // Now write the table
    Strm()
        .WriteChar('{')
        .WriteOString(OOO_STRING_SVTOOLS_RTF_IGNORE)
        .WriteOString(OOO_STRING_SVTOOLS_RTF_REVTBL)
        .WriteChar(' ');
    for (std::size_t i = 0; i < m_aRedlineTable.size(); ++i)
    {
        const OUString* pAuthor = GetRedline(i);
        Strm().WriteChar('{');
        if (pAuthor)
        {
            OUString sAuthor(bRemoveChangesInfo ? "Author" + OUString::number(GetInfoID(*pAuthor))
                                                : *pAuthor);
            Strm().WriteOString(msfilter::rtfutil::OutString(sAuthor, m_eDefaultEncoding));
        }
        Strm().WriteOString(";}");
    }
    Strm().WriteChar('}').WriteOString(SAL_NEWLINE_STRING);
}
 
void RtfExport::WriteHeadersFooters(sal_uInt8 nHeadFootFlags, const SwFrameFormat& rFormat,
                                    const SwFrameFormat& rLeftHeaderFormat,
                                    const SwFrameFormat& rLeftFooterFormat,
                                    const SwFrameFormat& rFirstPageFormat, sal_uInt8 /*nBreakCode*/,
                                    bool /*bEvenAndOddHeaders*/)
{
    // headers
    if (nHeadFootFlags & nsHdFtFlags::WW8_HEADER_EVEN)
        WriteHeaderFooter(rLeftHeaderFormat, true, OOO_STRING_SVTOOLS_RTF_HEADERL);
 
    if (nHeadFootFlags & nsHdFtFlags::WW8_HEADER_ODD)
        WriteHeaderFooter(rFormat, true, OOO_STRING_SVTOOLS_RTF_HEADER);
 
    if (nHeadFootFlags & nsHdFtFlags::WW8_HEADER_FIRST)
        WriteHeaderFooter(rFirstPageFormat, true, OOO_STRING_SVTOOLS_RTF_HEADERF, true);
 
    // footers
    if (nHeadFootFlags & nsHdFtFlags::WW8_FOOTER_EVEN)
        WriteHeaderFooter(rLeftFooterFormat, false, OOO_STRING_SVTOOLS_RTF_FOOTERL);
 
    if (nHeadFootFlags & nsHdFtFlags::WW8_FOOTER_ODD)
        WriteHeaderFooter(rFormat, false, OOO_STRING_SVTOOLS_RTF_FOOTER);
 
    if (nHeadFootFlags & nsHdFtFlags::WW8_FOOTER_FIRST)
        WriteHeaderFooter(rFirstPageFormat, false, OOO_STRING_SVTOOLS_RTF_FOOTERF, true);
}
 
void RtfExport::OutputField(const SwField* pField, ww::eField eFieldType, const OUString& rFieldCmd,
                            FieldFlags nMode)
{
    m_pAttrOutput->WriteField_Impl(pField, eFieldType, rFieldCmd, nMode);
}
 
void RtfExport::WriteFormData(const ::sw::mark::Fieldmark& rFieldmark)
{
    sal_Int32 nType;
    if (rFieldmark.GetFieldname() == ODF_FORMDROPDOWN)
    {
        nType = 2;
    }
    /* TODO
    else if (rFieldmark.GetFieldname() == ODF_FORMCHECKBOX)
    {
        nType = 1;
    }
    else if (rFieldmark.GetFieldname() == ODF_FORMTEXT)
    {
        nType = 0;
    }
*/
    else
    {
        SAL_INFO("sw.rtf", "unknown field type");
        return;
    }
    m_pAttrOutput->RunText().append(
        "{" OOO_STRING_SVTOOLS_RTF_IGNORE OOO_STRING_SVTOOLS_RTF_FORMFIELD
        "{" OOO_STRING_SVTOOLS_RTF_FFTYPE);
    m_pAttrOutput->RunText().append(nType);
    if (rFieldmark.GetFieldname() == ODF_FORMDROPDOWN)
    {
        m_pAttrOutput->RunText().append(OOO_STRING_SVTOOLS_RTF_FFHASLISTBOX "1");
        uno::Sequence<OUString> entries;
        if (auto const it = rFieldmark.GetParameters()->find(ODF_FORMDROPDOWN_LISTENTRY);
            it != rFieldmark.GetParameters()->end())
        {
            it->second >>= entries;
        }
        if (auto const it = rFieldmark.GetParameters()->find(ODF_FORMDROPDOWN_RESULT);
            it != rFieldmark.GetParameters()->end())
        {
            sal_Int32 result(-1);
            it->second >>= result;
            if (0 <= result && result < entries.getLength())
            {
                m_pAttrOutput->RunText().append(OOO_STRING_SVTOOLS_RTF_FFRES);
                m_pAttrOutput->RunText().append(result);
            }
        }
        for (OUString const& rEntry : entries)
        {
            m_pAttrOutput->RunText().append(
                "{" OOO_STRING_SVTOOLS_RTF_IGNORE OOO_STRING_SVTOOLS_RTF_FFL " ");
            m_pAttrOutput->RunText().append(
                msfilter::rtfutil::OutString(rEntry, m_eDefaultEncoding));
            m_pAttrOutput->RunText().append("}");
        }
    }
    m_pAttrOutput->RunText().append("}}"); // close FORMFIELD destination
}
 
void RtfExport::WriteHyperlinkData(const ::sw::mark::Fieldmark& /*rFieldmark*/)
{
    SAL_INFO("sw.rtf", "TODO: " << __func__);
}
 
void RtfExport::DoComboBox(const OUString& /*rName*/, const OUString& /*rHelp*/,
                           const OUString& /*rToolTip*/, const OUString& /*rSelected*/,
                           const uno::Sequence<OUString>& /*rListItems*/)
{
    // this is handled in RtfAttributeOutput::OutputFlyFrame_Impl
}
 
void RtfExport::DoFormText(const SwInputField* pField)
{
    OUString sResult = pField->ExpandField(true, nullptr);
    const OUString& rHelp = pField->GetHelp();
    OUString sName = pField->GetPar2();
    const OUString& rStatus = pField->GetToolTip();
    m_pAttrOutput->RunText().append("{" OOO_STRING_SVTOOLS_RTF_FIELD
                                    "{" OOO_STRING_SVTOOLS_RTF_IGNORE OOO_STRING_SVTOOLS_RTF_FLDINST
                                    "{ FORMTEXT }");
    m_pAttrOutput->RunText().append(
        "{" OOO_STRING_SVTOOLS_RTF_IGNORE OOO_STRING_SVTOOLS_RTF_FORMFIELD
        " {" OOO_STRING_SVTOOLS_RTF_FFTYPE "0");
    if (!rHelp.isEmpty())
        m_pAttrOutput->RunText().append(OOO_STRING_SVTOOLS_RTF_FFOWNHELP);
    if (!rStatus.isEmpty())
        m_pAttrOutput->RunText().append(OOO_STRING_SVTOOLS_RTF_FFOWNSTAT);
    m_pAttrOutput->RunText().append(OOO_STRING_SVTOOLS_RTF_FFTYPETXT "0");
 
    if (!sName.isEmpty())
        m_pAttrOutput->RunText().append(
            "{" OOO_STRING_SVTOOLS_RTF_IGNORE OOO_STRING_SVTOOLS_RTF_FFNAME " "
            + msfilter::rtfutil::OutString(sName, m_eDefaultEncoding) + "}");
    if (!rHelp.isEmpty())
        m_pAttrOutput->RunText().append(
            "{" OOO_STRING_SVTOOLS_RTF_IGNORE OOO_STRING_SVTOOLS_RTF_FFHELPTEXT " "
            + msfilter::rtfutil::OutString(rHelp, m_eDefaultEncoding) + "}");
    m_pAttrOutput->RunText().append(
        "{" OOO_STRING_SVTOOLS_RTF_IGNORE OOO_STRING_SVTOOLS_RTF_FFDEFTEXT " "
        + msfilter::rtfutil::OutString(sResult, m_eDefaultEncoding) + "}");
    if (!rStatus.isEmpty())
        m_pAttrOutput->RunText().append(
            "{" OOO_STRING_SVTOOLS_RTF_IGNORE OOO_STRING_SVTOOLS_RTF_FFSTATTEXT " "
            + msfilter::rtfutil::OutString(rStatus, m_eDefaultEncoding) + "}");
    m_pAttrOutput->RunText().append("}}}{" OOO_STRING_SVTOOLS_RTF_FLDRSLT " ");
    m_pAttrOutput->RunText().append(msfilter::rtfutil::OutString(sResult, m_eDefaultEncoding)
                                    + "}}");
}
 
sal_uInt64 RtfExport::ReplaceCr(sal_uInt8 /*nChar*/)
{
    // Completely unused for Rtf export... only here for code sharing
    // purpose with binary export
 
    return 0;
}
 
void RtfExport::WriteFonts()
{
    Strm()
        .WriteOString(SAL_NEWLINE_STRING)
        .WriteChar('{')
        .WriteOString(OOO_STRING_SVTOOLS_RTF_FONTTBL);
    m_aFontHelper.WriteFontTable(*m_pAttrOutput);
    Strm().WriteChar('}');
}
 
void RtfExport::WriteStyles()
{
    SAL_INFO("sw.rtf", __func__ << " start");
    m_pStyles->OutputStylesTable();
    SAL_INFO("sw.rtf", __func__ << " end");
}
 
void RtfExport::WriteFootnoteSettings()
{
    const SwPageFootnoteInfo& rFootnoteInfo = m_rDoc.GetPageDesc(0).GetFootnoteInfo();
    // Request a separator only in case the width is larger than zero.
    bool bSeparator = double(rFootnoteInfo.GetWidth()) > 0;
 
    Strm()
        .WriteChar('{')
        .WriteOString(OOO_STRING_SVTOOLS_RTF_IGNORE)
        .WriteOString(OOO_STRING_SVTOOLS_RTF_FTNSEP);
    if (bSeparator)
        Strm().WriteOString(OOO_STRING_SVTOOLS_RTF_CHFTNSEP);
    Strm().WriteChar('}');
}
 
void RtfExport::WriteMainText()
{
    SAL_INFO("sw.rtf", __func__ << " start");
 
    const std::unique_ptr<SvxBrushItem> oBrush = getBackground();
    if (oBrush && oBrush->GetColor() != COL_AUTO)
    {
        Strm().WriteOString(LO_STRING_SVTOOLS_RTF_VIEWBKSP).WriteChar('1');
        Strm().WriteOString("{" OOO_STRING_SVTOOLS_RTF_IGNORE OOO_STRING_SVTOOLS_RTF_BACKGROUND);
        Strm().WriteOString("{" OOO_STRING_SVTOOLS_RTF_SHP);
        Strm().WriteOString("{" OOO_STRING_SVTOOLS_RTF_IGNORE OOO_STRING_SVTOOLS_RTF_SHPINST);
 
        std::vector<std::pair<OString, OString>> aProperties{
            { "shapeType", "1" },
            { "fillColor", OString::number(wwUtility::RGBToBGR(oBrush->GetColor())) }
        };
        for (const std::pair<OString, OString>& rPair : aProperties)
        {
            Strm().WriteOString("{" OOO_STRING_SVTOOLS_RTF_SP "{");
            Strm().WriteOString(OOO_STRING_SVTOOLS_RTF_SN " ");
            Strm().WriteOString(rPair.first);
            Strm().WriteOString("}{" OOO_STRING_SVTOOLS_RTF_SV " ");
            Strm().WriteOString(rPair.second);
            Strm().WriteOString("}}");
        }
        Strm().WriteChar('}'); // shpinst
        Strm().WriteChar('}'); // shp
        Strm().WriteChar('}'); // background
    }
 
    SwTableNode* pTableNode = m_pCurPam->GetPointNode().FindTableNode();
    if (m_pWriter && m_pWriter->m_bWriteOnlyFirstTable && pTableNode != nullptr)
    {
        m_pCurPam->GetPoint()->Assign(*pTableNode);
        m_pCurPam->GetMark()->Assign(*pTableNode->EndOfSectionNode());
    }
    else
    {
        m_pCurPam->GetPoint()->Assign(*m_rDoc.GetNodes().GetEndOfContent().StartOfSectionNode());
    }
 
    WriteText();
 
    SAL_INFO("sw.rtf", __func__ << " end");
}
 
void RtfExport::WriteInfo()
{
    OString aGenerator
        = OUStringToOString(utl::DocInfoHelper::GetGeneratorString(), RTL_TEXTENCODING_UTF8);
    Strm()
        .WriteOString("{" OOO_STRING_SVTOOLS_RTF_IGNORE LO_STRING_SVTOOLS_RTF_GENERATOR " ")
        .WriteOString(aGenerator)
        .WriteChar('}');
    Strm().WriteChar('{').WriteOString(OOO_STRING_SVTOOLS_RTF_INFO);
 
    SwDocShell* pDocShell(m_rDoc.GetDocShell());
    uno::Reference<document::XDocumentProperties> xDocProps;
    if (pDocShell)
    {
        uno::Reference<document::XDocumentPropertiesSupplier> xDPS(pDocShell->GetModel(),
                                                                   uno::UNO_QUERY);
        xDocProps.set(xDPS->getDocumentProperties());
    }
 
    if (xDocProps.is())
    {
        // Handle user-defined properties.
        uno::Reference<beans::XPropertyContainer> xUserDefinedProperties
            = xDocProps->getUserDefinedProperties();
        if (xUserDefinedProperties.is())
        {
            uno::Reference<beans::XPropertySet> xPropertySet(xUserDefinedProperties,
                                                             uno::UNO_QUERY);
            uno::Reference<beans::XPropertySetInfo> xPropertySetInfo
                = xPropertySet->getPropertySetInfo();
            // Do we have explicit markup in RTF for this property name?
            if (xPropertySetInfo->hasPropertyByName(u"Company"_ustr))
            {
                OUString aValue;
                xPropertySet->getPropertyValue(u"Company"_ustr) >>= aValue;
                OutUnicode(OOO_STRING_SVTOOLS_RTF_IGNORE OOO_STRING_SVTOOLS_RTF_COMPANY, aValue);
            }
        }
 
        OutUnicode(OOO_STRING_SVTOOLS_RTF_TITLE, xDocProps->getTitle(), true);
        OutUnicode(OOO_STRING_SVTOOLS_RTF_SUBJECT, xDocProps->getSubject());
 
        OutUnicode(OOO_STRING_SVTOOLS_RTF_KEYWORDS,
                   ::comphelper::string::convertCommaSeparated(xDocProps->getKeywords()));
        OutUnicode(OOO_STRING_SVTOOLS_RTF_DOCCOMM, xDocProps->getDescription());
 
        OutUnicode(OOO_STRING_SVTOOLS_RTF_AUTHOR, xDocProps->getAuthor());
        OutDateTime(OOO_STRING_SVTOOLS_RTF_CREATIM, xDocProps->getCreationDate());
 
        OutUnicode(OOO_STRING_SVTOOLS_RTF_AUTHOR, xDocProps->getModifiedBy());
        OutDateTime(OOO_STRING_SVTOOLS_RTF_REVTIM, xDocProps->getModificationDate());
 
        OutDateTime(OOO_STRING_SVTOOLS_RTF_PRINTIM, xDocProps->getPrintDate());
    }
 
    Strm().WriteChar('}');
}
 
void RtfExport::WriteUserPropType(int nType)
{
    Strm().WriteOString(OOO_STRING_SVTOOLS_RTF_PROPTYPE).WriteNumberAsString(nType);
}
 
void RtfExport::WriteUserPropValue(std::u16string_view rValue)
{
    Strm().WriteOString("{" OOO_STRING_SVTOOLS_RTF_STATICVAL " ");
    Strm().WriteOString(msfilter::rtfutil::OutString(rValue, m_eDefaultEncoding));
    Strm().WriteChar('}');
}
 
void RtfExport::WriteUserProps()
{
    Strm().WriteChar('{').WriteOString(
        OOO_STRING_SVTOOLS_RTF_IGNORE OOO_STRING_SVTOOLS_RTF_USERPROPS);
 
    SwDocShell* pDocShell(m_rDoc.GetDocShell());
    uno::Reference<document::XDocumentProperties> xDocProps;
    if (pDocShell)
    {
        uno::Reference<document::XDocumentPropertiesSupplier> xDPS(pDocShell->GetModel(),
                                                                   uno::UNO_QUERY);
        xDocProps.set(xDPS->getDocumentProperties());
    }
    else
    {
        // Clipboard document, read metadata from the meta field manager.
        sw::MetaFieldManager& rManager = m_rDoc.GetMetaFieldManager();
        xDocProps.set(rManager.getDocumentProperties());
    }
 
    if (xDocProps.is())
    {
        // Handle user-defined properties.
        uno::Reference<beans::XPropertyContainer> xUserDefinedProperties
            = xDocProps->getUserDefinedProperties();
        if (xUserDefinedProperties.is())
        {
            uno::Reference<beans::XPropertySet> xPropertySet(xUserDefinedProperties,
                                                             uno::UNO_QUERY);
            const uno::Sequence<beans::Property> aProperties
                = xPropertySet->getPropertySetInfo()->getProperties();
 
            for (const beans::Property& rProperty : aProperties)
            {
                if (rProperty.Name.startsWith("Company"))
                    // We have explicit markup in RTF for this property.
                    continue;
 
                // Property name.
                Strm().WriteOString("{" OOO_STRING_SVTOOLS_RTF_PROPNAME " ");
                Strm().WriteOString(
                    msfilter::rtfutil::OutString(rProperty.Name, m_eDefaultEncoding));
                Strm().WriteChar('}');
 
                // Property value.
                OUString aValue;
                double fValue;
                bool bValue;
                util::DateTime aDate;
                uno::Any aAny = xPropertySet->getPropertyValue(rProperty.Name);
                if (aAny >>= bValue)
                {
                    WriteUserPropType(11);
                    WriteUserPropValue(OUString::number(static_cast<int>(bValue)));
                }
                else if (aAny >>= aValue)
                {
                    WriteUserPropType(30);
                    WriteUserPropValue(aValue);
                }
                else if (aAny >>= fValue)
                {
                    aValue = OUString::number(fValue);
                    if (aValue.indexOf('.') == -1)
                    {
                        // Integer.
                        WriteUserPropType(3);
                        WriteUserPropValue(aValue);
                    }
                    else
                    {
                        // Real number.
                        WriteUserPropType(5);
                        WriteUserPropValue(aValue);
                    }
                }
                else if (aAny >>= aDate)
                {
                    WriteUserPropType(64);
                    // Format is 'YYYY. MM. DD.'.
                    aValue += OUString::number(aDate.Year) + ". ";
                    if (aDate.Month < 10)
                        aValue += "0";
                    aValue += OUString::number(aDate.Month) + ". ";
                    if (aDate.Day < 10)
                        aValue += "0";
                    aValue += OUString::number(aDate.Day) + ".";
                    WriteUserPropValue(aValue);
                }
            }
        }
    }
 
    Strm().WriteChar('}');
}
 
void RtfExport::WriteDocVars()
{
    SwDocShell* pDocShell(m_rDoc.GetDocShell());
    if (!pDocShell)
        return;
 
    uno::Reference<text::XTextFieldsSupplier> xModel(pDocShell->GetModel(), uno::UNO_QUERY);
    uno::Reference<container::XNameAccess> xTextFieldMasters = xModel->getTextFieldMasters();
    uno::Sequence<rtl::OUString> aMasterNames = xTextFieldMasters->getElementNames();
    if (!aMasterNames.hasElements())
    {
        return;
    }
 
    // Only write docVars if there will be at least a single docVar.
    static constexpr OUString aPrefix(u"com.sun.star.text.fieldmaster.User."_ustr);
    for (const auto& rMasterName : aMasterNames)
    {
        if (!rMasterName.startsWith(aPrefix))
        {
            // Not a user field.
            continue;
        }
 
        uno::Reference<beans::XPropertySet> xField;
        xTextFieldMasters->getByName(rMasterName) >>= xField;
        if (!xField.is())
        {
            continue;
        }
 
        OUString aKey = rMasterName.copy(aPrefix.getLength());
        OUString aValue;
        xField->getPropertyValue(u"Content"_ustr) >>= aValue;
 
        Strm().WriteChar('{').WriteOString(
            OOO_STRING_SVTOOLS_RTF_IGNORE OOO_STRING_SVTOOLS_RTF_DOCVAR);
        Strm().WriteChar(' ');
 
        Strm().WriteChar('{');
        Strm().WriteOString(msfilter::rtfutil::OutString(aKey, m_eDefaultEncoding));
        Strm().WriteChar('}');
 
        Strm().WriteChar('{');
        Strm().WriteOString(msfilter::rtfutil::OutString(aValue, m_eDefaultEncoding));
        Strm().WriteChar('}');
 
        Strm().WriteChar('}');
    }
}
 
ErrCode RtfExport::ExportDocument_Impl()
{
    // Make the header
    Strm()
        .WriteChar('{')
        .WriteOString(OOO_STRING_SVTOOLS_RTF_RTF)
        .WriteChar('1')
        .WriteOString(OOO_STRING_SVTOOLS_RTF_ANSI);
    Strm().WriteOString(OOO_STRING_SVTOOLS_RTF_DEFF);
    Strm().WriteNumberAsString(
        m_aFontHelper.GetId(m_rDoc.GetAttrPool().GetUserOrPoolDefaultItem(RES_CHRATR_FONT)));
    // If this not exist, MS don't understand our ansi characters (0x80-0xff).
    Strm().WriteOString("\\adeflang1025");
 
    // Font table
    WriteFonts();
 
    m_pStyles = std::make_unique<MSWordStyles>(*this);
    // Color and stylesheet table
    WriteStyles();
 
    // List table
    BuildNumbering();
    WriteNumbering();
 
    WriteRevTab();
 
    WriteInfo();
    WriteUserProps();
    WriteDocVars();
 
    // Default TabSize
    Strm().WriteOString(m_pAttrOutput->GetTabStop()).WriteOString(SAL_NEWLINE_STRING);
    m_pAttrOutput->GetTabStop().setLength(0);
 
    // Automatic hyphenation: it's a global setting in Word, it's a paragraph setting in Writer.
    // Set it's value to "auto" and disable on paragraph level, if no hyphenation is used there.
    Strm().WriteOString(OOO_STRING_SVTOOLS_RTF_HYPHAUTO);
    Strm().WriteOString("1");
 
    // Zoom
    SwViewShell* pViewShell(m_rDoc.getIDocumentLayoutAccess().GetCurrentViewShell());
    if (pViewShell && pViewShell->GetViewOptions()->GetZoomType() == SvxZoomType::PERCENT)
    {
        Strm().WriteOString(OOO_STRING_SVTOOLS_RTF_VIEWSCALE);
        Strm().WriteNumberAsString(pViewShell->GetViewOptions()->GetZoom());
    }
    // Record changes?
    if (RedlineFlags::On & m_nOrigRedlineFlags)
        Strm().WriteOString(OOO_STRING_SVTOOLS_RTF_REVISIONS);
    // Mirror margins?
    if ((UseOnPage::Mirror & m_rDoc.GetPageDesc(0).ReadUseOn()) == UseOnPage::Mirror)
        Strm().WriteOString(OOO_STRING_SVTOOLS_RTF_MARGMIRROR);
 
    // Gutter at top?
    IDocumentSettingAccess& rIDSA = m_rDoc.getIDocumentSettingAccess();
    if (rIDSA.get(DocumentSettingId::GUTTER_AT_TOP))
    {
        Strm().WriteOString(LO_STRING_SVTOOLS_RTF_GUTTERPRL);
    }
 
    // Init sections
    m_pSections = std::make_unique<MSWordSections>(*this);
 
    // Enable form protection by default if needed, as there is no switch to
    // enable it on a per-section basis. OTOH don't always enable it as it
    // breaks moving of drawings - so write it only in case there is really a
    // protected section in the document.
    SwSectionFormats& rSections = m_rDoc.GetSections();
    for (auto const& pSectionFormat : rSections)
    {
        if (!pSectionFormat->IsInUndo() && pSectionFormat->GetProtect().IsContentProtected())
        {
            Strm().WriteOString(OOO_STRING_SVTOOLS_RTF_FORMPROT);
            break;
        }
    }
 
    // enable form field shading
    Strm().WriteOString(OOO_STRING_SVTOOLS_RTF_FORMSHADE);
 
    // Enable breaking wrapped tables across pages: the "no" in the control word's name is
    // confusing.
    if (!rIDSA.get(DocumentSettingId::DO_NOT_BREAK_WRAPPED_TABLES))
    {
        Strm().WriteOString(LO_STRING_SVTOOLS_RTF_NOBRKWRPTBL);
    }
 
    // size and empty margins of the page
    if (m_rDoc.GetPageDescCnt())
    {
        // Seeking the first SwFormatPageDesc. If no set, the default is valid
        const SwFormatPageDesc* pSttPgDsc = nullptr;
        {
            const SwNode& rSttNd
                = *m_rDoc.GetNodes()[m_rDoc.GetNodes().GetEndOfExtras().GetIndex() + 2];
            const SfxItemSet* pSet = nullptr;
 
            if (rSttNd.IsContentNode())
                pSet = &rSttNd.GetContentNode()->GetSwAttrSet();
            else if (rSttNd.IsTableNode())
                pSet = &rSttNd.GetTableNode()->GetTable().GetFrameFormat()->GetAttrSet();
 
            else if (rSttNd.IsSectionNode())
                pSet = &rSttNd.GetSectionNode()->GetSection().GetFormat()->GetAttrSet();
 
            if (pSet)
            {
                std::size_t nPosInDoc;
                pSttPgDsc = &pSet->Get(RES_PAGEDESC);
                if (!pSttPgDsc->GetPageDesc())
                    pSttPgDsc = nullptr;
                else if (m_rDoc.FindPageDesc(pSttPgDsc->GetPageDesc()->GetName(), &nPosInDoc))
                {
                    Strm()
                        .WriteChar('{')
                        .WriteOString(OOO_STRING_SVTOOLS_RTF_IGNORE)
                        .WriteOString(OOO_STRING_SVTOOLS_RTF_PGDSCNO);
                    Strm().WriteNumberAsString(nPosInDoc).WriteChar('}');
                }
            }
        }
        const SwPageDesc& rPageDesc = pSttPgDsc ? *pSttPgDsc->GetPageDesc() : m_rDoc.GetPageDesc(0);
        const SwFrameFormat& rFormatPage = rPageDesc.GetMaster();
 
        {
            if (rPageDesc.GetLandscape())
                Strm().WriteOString(OOO_STRING_SVTOOLS_RTF_LANDSCAPE);
 
            const SwFormatFrameSize& rSz = rFormatPage.GetFrameSize();
            // Clipboard document is always created without a printer, then
            // the size will be always LONG_MAX! Solution then is to use A4
            if (LONG_MAX == rSz.GetHeight() || LONG_MAX == rSz.GetWidth())
            {
                Strm().WriteOString(OOO_STRING_SVTOOLS_RTF_PAPERH);
                Size a4 = SvxPaperInfo::GetPaperSize(PAPER_A4);
                Strm().WriteNumberAsString(a4.Height()).WriteOString(OOO_STRING_SVTOOLS_RTF_PAPERW);
                Strm().WriteNumberAsString(a4.Width());
            }
            else
            {
                Strm().WriteOString(OOO_STRING_SVTOOLS_RTF_PAPERH);
                Strm()
                    .WriteNumberAsString(rSz.GetHeight())
                    .WriteOString(OOO_STRING_SVTOOLS_RTF_PAPERW);
                Strm().WriteNumberAsString(rSz.GetWidth());
            }
        }
 
        {
            const SvxLRSpaceItem& rLR = rFormatPage.GetLRSpace();
            Strm().WriteOString(OOO_STRING_SVTOOLS_RTF_MARGL);
            Strm().WriteNumberAsString(rLR.GetLeft()).WriteOString(OOO_STRING_SVTOOLS_RTF_MARGR);
            Strm().WriteNumberAsString(rLR.GetRight());
        }
 
        {
            const SvxULSpaceItem& rUL = rFormatPage.GetULSpace();
            Strm().WriteOString(OOO_STRING_SVTOOLS_RTF_MARGT);
            Strm().WriteNumberAsString(rUL.GetUpper()).WriteOString(OOO_STRING_SVTOOLS_RTF_MARGB);
            Strm().WriteNumberAsString(rUL.GetLower());
        }
 
        Strm()
            .WriteOString(OOO_STRING_SVTOOLS_RTF_SECTD)
            .WriteOString(OOO_STRING_SVTOOLS_RTF_SBKNONE);
        m_pAttrOutput->SectFootnoteEndnotePr();
        // All sections are unlocked by default
        Strm().WriteOString(OOO_STRING_SVTOOLS_RTF_SECTUNLOCKED);
        Strm().WriteOString("1");
        OutPageDescription(rPageDesc); // Changed bCheckForFirstPage to true so headers
        // following title page are correctly added - i13107
        if (pSttPgDsc)
        {
            m_pCurrentPageDesc = &rPageDesc;
        }
    }
 
    // line numbering
    const SwLineNumberInfo& rLnNumInfo = m_rDoc.GetLineNumberInfo();
    if (rLnNumInfo.IsPaintLineNumbers())
    {
        sal_uLong nLnNumRestartNo = 0;
        if (const WW8_SepInfo* pSectionInfo = m_pSections->CurrentSectionInfo())
            nLnNumRestartNo = pSectionInfo->nLnNumRestartNo;
 
        AttrOutput().SectionLineNumbering(nLnNumRestartNo, rLnNumInfo);
    }
 
    {
        // write the footnotes and endnotes-out Info
        const SwFootnoteInfo& rFootnoteInfo = m_rDoc.GetFootnoteInfo();
 
        const char* pOut = FTNPOS_CHAPTER == rFootnoteInfo.m_ePos ? OOO_STRING_SVTOOLS_RTF_ENDDOC
                                                                  : OOO_STRING_SVTOOLS_RTF_FTNBJ;
        Strm().WriteOString(pOut).WriteOString(OOO_STRING_SVTOOLS_RTF_FTNSTART);
        Strm().WriteNumberAsString(rFootnoteInfo.m_nFootnoteOffset + 1);
 
        switch (rFootnoteInfo.m_eNum)
        {
            case FTNNUM_PAGE:
                pOut = OOO_STRING_SVTOOLS_RTF_FTNRSTPG;
                break;
            case FTNNUM_DOC:
                pOut = OOO_STRING_SVTOOLS_RTF_FTNRSTCONT;
                break;
            default:
                pOut = OOO_STRING_SVTOOLS_RTF_FTNRESTART;
                break;
        }
        Strm().WriteOString(pOut);
 
        switch (rFootnoteInfo.m_aFormat.GetNumberingType())
        {
            case SVX_NUM_CHARS_LOWER_LETTER:
            case SVX_NUM_CHARS_LOWER_LETTER_N:
                pOut = OOO_STRING_SVTOOLS_RTF_FTNNALC;
                break;
            case SVX_NUM_CHARS_UPPER_LETTER:
            case SVX_NUM_CHARS_UPPER_LETTER_N:
                pOut = OOO_STRING_SVTOOLS_RTF_FTNNAUC;
                break;
            case SVX_NUM_ROMAN_LOWER:
                pOut = OOO_STRING_SVTOOLS_RTF_FTNNRLC;
                break;
            case SVX_NUM_ROMAN_UPPER:
                pOut = OOO_STRING_SVTOOLS_RTF_FTNNRUC;
                break;
            case SVX_NUM_SYMBOL_CHICAGO:
                pOut = OOO_STRING_SVTOOLS_RTF_FTNNCHI;
                break;
            default:
                pOut = OOO_STRING_SVTOOLS_RTF_FTNNAR;
                break;
        }
        Strm().WriteOString(pOut);
 
        const SwEndNoteInfo& rEndNoteInfo = m_rDoc.GetEndNoteInfo();
 
        if (!rSections.empty())
        {
            SwSectionFormat* pFormat = rSections[0];
            bool bEndnAtEnd = pFormat->GetEndAtTextEnd().IsAtEnd();
            if (bEndnAtEnd)
            {
                // Endnotes at end of section.
                Strm().WriteOString(OOO_STRING_SVTOOLS_RTF_AENDNOTES);
            }
            else
            {
                // Endnotes at end of document.
                Strm().WriteOString(OOO_STRING_SVTOOLS_RTF_AENDDOC);
            }
        }
 
        // Types of notes that are present in the document:
        Strm().WriteOString(OOO_STRING_SVTOOLS_RTF_FET);
        SwFootnoteIdxs& rFootnotes = m_rDoc.GetFootnoteIdxs();
        bool bHasFootnote = false;
        bool bHasEndnote = false;
        for (const auto& pFootnote : rFootnotes)
        {
            if (pFootnote->GetFootnote().IsEndNote())
            {
                bHasEndnote = true;
            }
            else
            {
                bHasFootnote = true;
            }
 
            if (bHasFootnote && bHasEndnote)
            {
                break;
            }
        }
        if (bHasFootnote && bHasEndnote)
        {
            // Both footnotes and endnotes.
            Strm().WriteOString("2");
        }
        else if (bHasEndnote)
        {
            // Endnotes only.
            Strm().WriteOString("1");
        }
 
        Strm()
            .WriteOString(OOO_STRING_SVTOOLS_RTF_AFTNRSTCONT)
            .WriteOString(OOO_STRING_SVTOOLS_RTF_AFTNSTART);
        Strm().WriteNumberAsString(rEndNoteInfo.m_nFootnoteOffset + 1);
 
        switch (rEndNoteInfo.m_aFormat.GetNumberingType())
        {
            case SVX_NUM_CHARS_LOWER_LETTER:
            case SVX_NUM_CHARS_LOWER_LETTER_N:
                pOut = OOO_STRING_SVTOOLS_RTF_AFTNNALC;
                break;
            case SVX_NUM_CHARS_UPPER_LETTER:
            case SVX_NUM_CHARS_UPPER_LETTER_N:
                pOut = OOO_STRING_SVTOOLS_RTF_AFTNNAUC;
                break;
            case SVX_NUM_ROMAN_LOWER:
                pOut = OOO_STRING_SVTOOLS_RTF_AFTNNRLC;
                break;
            case SVX_NUM_ROMAN_UPPER:
                pOut = OOO_STRING_SVTOOLS_RTF_AFTNNRUC;
                break;
            case SVX_NUM_SYMBOL_CHICAGO:
                pOut = OOO_STRING_SVTOOLS_RTF_AFTNNCHI;
                break;
            default:
                pOut = OOO_STRING_SVTOOLS_RTF_AFTNNAR;
                break;
        }
        Strm().WriteOString(pOut);
    }
 
    if (!m_rDoc.getIDocumentSettingAccess().get(DocumentSettingId::PARA_SPACE_MAX))
        // RTF default is true, so write compat flag if this should be false.
        Strm().WriteOString(LO_STRING_SVTOOLS_RTF_HTMAUTSP);
 
    Strm().WriteOString(SAL_NEWLINE_STRING);
 
    WriteFootnoteSettings();
 
    WriteMainText();
 
    Strm().WriteChar('}');
 
    return ERRCODE_NONE;
}
 
void RtfExport::PrepareNewPageDesc(const SfxItemSet* pSet, const SwNode& rNd,
                                   const SwFormatPageDesc* pNewPgDescFormat,
                                   const SwPageDesc* pNewPgDesc, bool bExtraPageBreak)
{
    const SwSectionFormat* pFormat = GetSectionFormat(rNd);
    const sal_uLong nLnNm = GetSectionLineNo(pSet, rNd);
 
    OSL_ENSURE(pNewPgDescFormat || pNewPgDesc, "Neither page desc format nor page desc provided.");
 
    if (pNewPgDescFormat)
        m_pSections->AppendSection(*pNewPgDescFormat, rNd, pFormat, nLnNm);
    else if (pNewPgDesc)
    {
        m_pSections->AppendSection(SwFormatPageDesc(pNewPgDesc), rNd, pFormat, nLnNm);
    }
 
    // Don't insert a page break, when we're changing page style just because the next page has to be a different one.
    if (!m_pAttrOutput->GetPrevPageDesc()
        || m_pAttrOutput->GetPrevPageDesc()->GetFollow() != pNewPgDesc)
        AttrOutput().SectionBreak(msword::PageBreak, false, m_pSections->CurrentSectionInfo(),
                                  bExtraPageBreak);
}
 
bool RtfExport::DisallowInheritingOutlineNumbering(const SwFormat& rFormat)
{
    bool bRet(false);
 
    if (SfxItemState::SET != rFormat.GetItemState(RES_PARATR_NUMRULE, false))
    {
        if (const SwFormat* pParent = rFormat.DerivedFrom())
        {
            if (static_cast<const SwTextFormatColl*>(pParent)
                    ->IsAssignedToListLevelOfOutlineStyle())
            {
                // Level 9 disables the outline
                Strm().WriteOString(OOO_STRING_SVTOOLS_RTF_LEVEL).WriteInt32(9);
 
                bRet = true;
            }
        }
    }
 
    return bRet;
}
 
void RtfExport::OutputEndNode(const SwEndNode& rEndNode)
{
    if (TXT_MAINTEXT == m_nTextTyp && rEndNode.StartOfSectionNode()->IsTableNode())
        // End node of a table: see if a section break should be written after the table.
        AttrOutput().SectionBreaks(rEndNode);
}
 
void RtfExport::OutputGrfNode(const SwGrfNode& /*rGrfNode*/)
{
    /* noop, see RtfAttributeOutput::FlyFrameGraphic */
}
 
void RtfExport::OutputOLENode(const SwOLENode& /*rOLENode*/)
{
    /* noop, see RtfAttributeOutput::FlyFrameOLE */
}
 
void RtfExport::OutputLinkedOLE(const OUString& /*rLinked*/) {}
 
void RtfExport::OutputTextNode(SwTextNode& rNode)
{
    m_nCurrentNodeIndex = rNode.GetIndex();
    if (!m_bOutOutlineOnly || rNode.IsOutline())
        MSWordExportBase::OutputTextNode(rNode);
    m_nCurrentNodeIndex = SwNodeOffset(0);
}
 
void RtfExport::AppendSection(const SwPageDesc* pPageDesc, const SwSectionFormat* pFormat,
                              sal_uLong nLnNum)
{
    m_pSections->AppendSection(pPageDesc, pFormat, nLnNum);
    AttrOutput().SectionBreak(msword::PageBreak, false, m_pSections->CurrentSectionInfo());
}
 
RtfExport::RtfExport(RtfExportFilter* pFilter, SwDoc& rDocument,
                     std::shared_ptr<SwUnoCursor>& pCurrentPam, SwPaM& rOriginalPam,
                     Writer* pWriter, bool bOutOutlineOnly)
    : MSWordExportBase(rDocument, pCurrentPam, &rOriginalPam)
    , m_pFilter(pFilter)
    , m_pWriter(pWriter)
    , m_bOutOutlineOnly(bOutOutlineOnly)
    , m_eDefaultEncoding(
          rtl_getTextEncodingFromWindowsCharset(sw::ms::rtl_TextEncodingToWinCharset(DEF_ENCODING)))
    , m_eCurrentEncoding(m_eDefaultEncoding)
    , m_bRTFFlySyntax(false)
    , m_nCurrentNodeIndex(0)
    , mpAuthorIDs(new SvtSecurityMapPersonalInfo)
{
    m_bExportModeRTF = true;
    // the attribute output for the document
    m_pAttrOutput = std::make_unique<RtfAttributeOutput>(*this);
    // that just causes problems for RTF
    m_bSubstituteBullets = false;
    // needed to have a complete font table
    m_aFontHelper.m_bLoadAllFonts = true;
    // the related SdrExport
    m_pSdrExport = std::make_unique<RtfSdrExport>(*this);
 
    if (!m_pWriter)
        m_pWriter = &m_pFilter->GetWriter();
}
 
RtfExport::~RtfExport() = default;
 
SvStream& RtfExport::Strm()
{
    if (m_pStream)
        return *m_pStream;
 
    return m_pWriter->Strm();
}
 
void RtfExport::setStream() { m_pStream = std::make_unique<SvMemoryStream>(); }
 
OString RtfExport::getStream()
{
    OString aRet;
 
    if (m_pStream)
        aRet = OString(static_cast<const char*>(m_pStream->GetData()), m_pStream->Tell());
 
    return aRet;
}
 
void RtfExport::resetStream() { m_pStream.reset(); }
 
void RtfExport::OutUnicode(std::string_view pToken, std::u16string_view rContent, bool bUpr)
{
    if (rContent.empty())
        return;
 
    if (!bUpr)
    {
        Strm().WriteChar('{').WriteOString(pToken).WriteChar(' ');
        Strm().WriteOString(msfilter::rtfutil::OutString(rContent, m_eCurrentEncoding));
        Strm().WriteChar('}');
    }
    else
        Strm().WriteOString(msfilter::rtfutil::OutStringUpr(pToken, rContent, m_eCurrentEncoding));
}
 
void RtfExport::OutDateTime(std::string_view pStr, const util::DateTime& rDT)
{
    Strm().WriteChar('{').WriteOString(pStr).WriteOString(OOO_STRING_SVTOOLS_RTF_YR);
    Strm().WriteNumberAsString(rDT.Year).WriteOString(OOO_STRING_SVTOOLS_RTF_MO);
    Strm().WriteNumberAsString(rDT.Month).WriteOString(OOO_STRING_SVTOOLS_RTF_DY);
    Strm().WriteNumberAsString(rDT.Day).WriteOString(OOO_STRING_SVTOOLS_RTF_HR);
    Strm().WriteNumberAsString(rDT.Hours).WriteOString(OOO_STRING_SVTOOLS_RTF_MIN);
    Strm().WriteNumberAsString(rDT.Minutes).WriteChar('}');
}
 
sal_uInt16 RtfExport::GetColor(const Color& rColor) const
{
    for (const auto& rEntry : m_aColTable)
        if (rEntry.second == rColor)
        {
            SAL_INFO("sw.rtf", __func__ << " returning " << rEntry.first << " (" << rColor.GetRed()
                                        << "," << rColor.GetGreen() << "," << rColor.GetBlue()
                                        << ")");
            return rEntry.first;
        }
    OSL_FAIL("No such Color in m_aColTable!");
    return 0;
}
 
void RtfExport::InsColor(const Color& rCol)
{
    sal_uInt16 n;
    bool bAutoColorInTable = false;
    for (const auto& rEntry : m_aColTable)
    {
        if (rEntry.second == rCol)
            return; // Already in the table
        if (rEntry.second == COL_AUTO)
            bAutoColorInTable = true;
    }
    if (rCol == COL_AUTO)
        // COL_AUTO gets value 0
        n = 0;
    else
    {
        // other colors get values >0
        n = m_aColTable.size();
        if (!bAutoColorInTable)
            // reserve value "0" for COL_AUTO (if COL_AUTO wasn't inserted until now)
            n++;
    }
    m_aColTable.insert(std::pair<sal_uInt16, Color>(n, rCol));
}
 
void RtfExport::InsColorLine(const SvxBoxItem& rBox)
{
    const editeng::SvxBorderLine* pLine = nullptr;
 
    if (rBox.GetTop())
    {
        pLine = rBox.GetTop();
        InsColor(pLine->GetColor());
    }
    if (rBox.GetBottom() && pLine != rBox.GetBottom())
    {
        pLine = rBox.GetBottom();
        InsColor(pLine->GetColor());
    }
    if (rBox.GetLeft() && pLine != rBox.GetLeft())
    {
        pLine = rBox.GetLeft();
        InsColor(pLine->GetColor());
    }
    if (rBox.GetRight() && pLine != rBox.GetRight())
        InsColor(rBox.GetRight()->GetColor());
}
 
void RtfExport::OutColorTable()
{
    // Build the table from rPool since the colors provided to
    // RtfAttributeOutput callbacks are too late.
    const SfxItemPool& rPool = m_rDoc.GetAttrPool();
 
    // MSO Word uses a default color table with 16 colors (which is used e.g. for highlighting)
    InsColor(COL_BLACK);
    InsColor(COL_LIGHTBLUE);
    InsColor(COL_LIGHTCYAN);
    InsColor(COL_LIGHTGREEN);
    InsColor(COL_LIGHTMAGENTA);
    InsColor(COL_LIGHTRED);
    InsColor(COL_YELLOW);
    InsColor(COL_WHITE);
    InsColor(COL_BLUE);
    InsColor(COL_CYAN);
    InsColor(COL_GREEN);
    InsColor(COL_MAGENTA);
    InsColor(COL_RED);
    InsColor(COL_BROWN);
    InsColor(COL_GRAY);
    InsColor(COL_LIGHTGRAY);
 
    // char color
    {
        auto pCol = GetDfltAttr(RES_CHRATR_COLOR);
        InsColor(pCol->GetValue());
        pCol = rPool.GetUserDefaultItem(RES_CHRATR_COLOR);
        if (pCol)
            InsColor(pCol->GetValue());
        m_rDoc.ForEachCharacterColorItem([this](const SvxColorItem& rColorItem) -> bool {
            InsColor(rColorItem.GetValue());
            return true;
        });
 
        auto pUnder = GetDfltAttr(RES_CHRATR_UNDERLINE);
        InsColor(pUnder->GetColor());
        m_rDoc.ForEachCharacterUnderlineItem([this](const SvxUnderlineItem& rUnder) -> bool {
            InsColor(rUnder.GetColor());
            return true;
        });
 
        auto pOver = GetDfltAttr(RES_CHRATR_OVERLINE);
        InsColor(pOver->GetColor());
        m_rDoc.ForEachOverlineItem([this](const SvxOverlineItem& rOver) -> bool {
            InsColor(rOver.GetColor());
            return true;
        });
    }
 
    // background color
 
    {
        const SvxBrushItem* pBackground = GetDfltAttr(RES_BACKGROUND);
        InsColor(pBackground->GetColor());
        pBackground = rPool.GetUserDefaultItem(RES_BACKGROUND);
        if (pBackground)
        {
            InsColor(pBackground->GetColor());
        }
        m_rDoc.ForEachBackgroundBrushItem([this](const SvxBrushItem& rBrush) -> bool {
            InsColor(rBrush.GetColor());
            return true;
        });
    }
    {
        const SvxBrushItem* pBackground = GetDfltAttr(RES_CHRATR_BACKGROUND);
        InsColor(pBackground->GetColor());
        pBackground = rPool.GetUserDefaultItem(RES_CHRATR_BACKGROUND);
        if (pBackground)
        {
            InsColor(pBackground->GetColor());
        }
        m_rDoc.ForEachCharacterBrushItem([this](const SvxBrushItem& rBrush) -> bool {
            InsColor(rBrush.GetColor());
            return true;
        });
    }
 
    // shadow color
    {
        auto pShadow = GetDfltAttr(RES_SHADOW);
        InsColor(pShadow->GetColor());
        pShadow = rPool.GetUserDefaultItem(RES_SHADOW);
        if (nullptr != pShadow)
        {
            InsColor(pShadow->GetColor());
        }
        m_rDoc.ForEachShadowItem([this](const SvxShadowItem& rShadow) -> bool {
            InsColor(rShadow.GetColor());
            return true;
        });
    }
 
    // frame border color
    {
        const SvxBoxItem* pBox = rPool.GetUserDefaultItem(RES_BOX);
        if (nullptr != pBox)
            InsColorLine(*pBox);
        m_rDoc.ForEachBoxItem([this](const SvxBoxItem& rBox) -> bool {
            InsColorLine(rBox);
            return true;
        });
    }
 
    {
        const SvxBoxItem* pCharBox = rPool.GetUserDefaultItem(RES_CHRATR_BOX);
        if (pCharBox)
            InsColorLine(*pCharBox);
        m_rDoc.ForEachCharacterBoxItem([this](const SvxBoxItem& rCharBox) -> bool {
            InsColorLine(rCharBox);
            return true;
        });
    }
 
    // TextFrame or paragraph background solid fill.
    ItemSurrogates aSurrogates;
    rPool.GetItemSurrogatesForItem(aSurrogates, SfxItemType::XFillColorItemType);
    for (const SfxPoolItem* pItem : aSurrogates)
    {
        const auto& rColorItem = static_cast<const XFillColorItem&>(*pItem);
        InsColor(rColorItem.GetColorValue());
    }
 
    for (std::size_t n = 0; n < m_aColTable.size(); ++n)
    {
        const Color& rCol = m_aColTable[n];
        if (n || COL_AUTO != rCol)
        {
            Strm().WriteOString(OOO_STRING_SVTOOLS_RTF_RED);
            Strm().WriteNumberAsString(rCol.GetRed()).WriteOString(OOO_STRING_SVTOOLS_RTF_GREEN);
            Strm().WriteNumberAsString(rCol.GetGreen()).WriteOString(OOO_STRING_SVTOOLS_RTF_BLUE);
            Strm().WriteNumberAsString(rCol.GetBlue());
        }
        Strm().WriteChar(';');
    }
}
 
void RtfExport::InsStyle(sal_uInt16 nId, const OString& rStyle)
{
    m_aStyTable.insert(std::pair<sal_uInt16, OString>(nId, rStyle));
}
 
OString* RtfExport::GetStyle(sal_uInt16 nId)
{
    auto it = m_aStyTable.find(nId);
    if (it != m_aStyTable.end())
        return &it->second;
    return nullptr;
}
 
sal_uInt16 RtfExport::GetRedline(const OUString& rAuthor)
{
    const sal_uInt16 nId = m_aRedlineTable.size();
    // insert if we don't already have one
    auto[it, inserted] = m_aRedlineTable.insert(std::pair<OUString, sal_uInt16>(rAuthor, nId));
    (void)inserted;
    return it->second;
}
 
const OUString* RtfExport::GetRedline(sal_uInt16 nId)
{
    for (const auto& rEntry : m_aRedlineTable)
        if (rEntry.second == nId)
            return &rEntry.first;
    return nullptr;
}
 
void RtfExport::OutPageDescription(const SwPageDesc& rPgDsc)
{
    SAL_INFO("sw.rtf", __func__ << " start");
    const SwPageDesc* pSave = m_pCurrentPageDesc;
 
    m_pCurrentPageDesc = &rPgDsc;
    if (m_pCurrentPageDesc->GetFollow() && m_pCurrentPageDesc->GetFollow() != m_pCurrentPageDesc)
        m_pCurrentPageDesc = m_pCurrentPageDesc->GetFollow();
 
    if (m_pCurrentPageDesc->GetLandscape())
        Strm().WriteOString(OOO_STRING_SVTOOLS_RTF_LNDSCPSXN);
 
    const SwFormat* pFormat = &m_pCurrentPageDesc->GetMaster(); //GetLeft();
    m_bOutPageDescs = true;
    if (m_pCurrentPageDesc != &rPgDsc)
        m_pFirstPageItemSet = &rPgDsc.GetMaster().GetAttrSet();
    OutputFormat(*pFormat, true, false);
    m_pFirstPageItemSet = nullptr;
    m_bOutPageDescs = false;
 
    const bool bFakeFirst = m_pCurrentPageDesc != &rPgDsc;
    if (bFakeFirst || !m_pCurrentPageDesc->IsFirstShared())
        Strm().WriteOString(OOO_STRING_SVTOOLS_RTF_TITLEPG);
 
    // normal header / footer (without a style)
    const SfxPoolItem* pItem;
    if (m_pCurrentPageDesc->GetLeft().GetAttrSet().GetItemState(RES_HEADER, false, &pItem)
        == SfxItemState::SET)
        WriteHeaderFooter(*pItem, /*Header*/ true, /*AsTitlePg*/ false, /*WriteFirst*/ !bFakeFirst);
    if (m_pCurrentPageDesc->GetLeft().GetAttrSet().GetItemState(RES_FOOTER, false, &pItem)
        == SfxItemState::SET)
        WriteHeaderFooter(*pItem, false, /*AsTitlePg*/ false, /*WriteFirst*/ !bFakeFirst);
 
    // title page
    if (bFakeFirst)
    {
        m_pCurrentPageDesc = &rPgDsc;
        if (m_pCurrentPageDesc->GetMaster().GetAttrSet().GetItemState(RES_HEADER, false, &pItem)
            == SfxItemState::SET)
            WriteHeaderFooter(*pItem, /*Header*/ true, /*AsTitlePg*/ true, /*WriteFirst*/ false);
        if (m_pCurrentPageDesc->GetMaster().GetAttrSet().GetItemState(RES_FOOTER, false, &pItem)
            == SfxItemState::SET)
            WriteHeaderFooter(*pItem, /*Header*/ false, /*AsTitlePg*/ true, /*WriteFirst*/ false);
    }
 
    // numbering type
    AttrOutput().SectionPageNumbering(m_pCurrentPageDesc->GetNumType().GetNumberingType(),
                                      std::nullopt);
 
    m_pCurrentPageDesc = pSave;
    SAL_INFO("sw.rtf", __func__ << " end");
}
 
/** WriteHeaderFooter: used to write the initial header and footers
 *  @param bHeader: true for a header, false for a footer.
 *  @param bAsTitlePg: used to emulate a first-follow page style linking.
 *      Set to true to only write this header as if it were a "first header".
 *  @param bWriteFirst: used to determine whether to write a non-shared first header as the header.
 *      Set to false if bAsTitlePg will be used to define the "first header".
 */
void RtfExport::WriteHeaderFooter(const SfxPoolItem& rItem, bool bHeader, bool bAsTitlePg,
                                  bool bWriteFirst)
{
    assert(!bAsTitlePg || !bWriteFirst);
 
    if (bHeader)
    {
        const auto& rHeader = static_cast<const SwFormatHeader&>(rItem);
        if (!rHeader.IsActive())
            return;
    }
    else
    {
        const auto& rFooter = static_cast<const SwFormatFooter&>(rItem);
        if (!rFooter.IsActive())
            return;
    }
 
    SAL_INFO("sw.rtf", __func__ << " start");
 
    /* is this a title page? */
    if (bAsTitlePg || (bWriteFirst && !m_pCurrentPageDesc->IsFirstShared()))
    {
        auto pStr = bHeader ? OOO_STRING_SVTOOLS_RTF_HEADERF : OOO_STRING_SVTOOLS_RTF_FOOTERF;
        Strm().WriteChar('{').WriteOString(pStr);
        WriteHeaderFooterText(m_pCurrentPageDesc->IsFirstShared()
                                  ? m_pCurrentPageDesc->GetMaster()
                                  : m_pCurrentPageDesc->GetFirstMaster(),
                              bHeader);
        Strm().WriteChar('}');
    }
 
    if (!bAsTitlePg)
    {
        auto pStr = bHeader ? OOO_STRING_SVTOOLS_RTF_HEADER : OOO_STRING_SVTOOLS_RTF_FOOTER;
        Strm().WriteChar('{').WriteOString(pStr);
        WriteHeaderFooterText(m_pCurrentPageDesc->GetMaster(), bHeader);
        Strm().WriteChar('}');
    }
 
    SAL_INFO("sw.rtf", __func__ << " end");
}
 
void RtfExport::WriteHeaderFooter(const SwFrameFormat& rFormat, bool bHeader, const char* pStr,
                                  bool bTitlepg)
{
    SAL_INFO("sw.rtf", __func__ << " start");
 
    m_pAttrOutput->WriteHeaderFooter_Impl(rFormat, bHeader, pStr, bTitlepg);
 
    SAL_INFO("sw.rtf", __func__ << " end");
}
 
namespace
{
/// Glue class to call RtfExport as an internal filter, needed by copy&paste support.
class SwRTFWriter : public Writer
{
private:
    bool m_bOutOutlineOnly;
 
public:
    SwRTFWriter(std::u16string_view rFilterName, const OUString& rBaseURL);
 
    ErrCode WriteStream() override;
};
}
 
SwRTFWriter::SwRTFWriter(std::u16string_view rFilterName, const OUString& rBaseURL)
{
    SetBaseURL(rBaseURL);
    // export outline nodes, only (send outline to clipboard/presentation)
    m_bOutOutlineOnly = o3tl::starts_with(rFilterName, u"O");
}
 
ErrCode SwRTFWriter::WriteStream()
{
    std::shared_ptr<SwUnoCursor> pCurPam(m_pDoc->CreateUnoCursor(*m_pCurrentPam->End(), false));
    pCurPam->SetMark();
    *pCurPam->GetPoint() = *m_pCurrentPam->Start();
    RtfExport aExport(nullptr, *m_pDoc, pCurPam, *m_pCurrentPam, this, m_bOutOutlineOnly);
    aExport.ExportDocument(true);
    return ERRCODE_NONE;
}
 
extern "C" SAL_DLLPUBLIC_EXPORT void ExportRTF(std::u16string_view rFltName,
                                               const OUString& rBaseURL, WriterRef& xRet)
{
    xRet = new SwRTFWriter(rFltName, rBaseURL);
}
 
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */

V530 The return value of function 'append' is required to be utilized.

V530 The return value of function 'append' is required to be utilized.

V530 The return value of function 'ExportDocument' is required to be utilized.

V560 A part of conditional expression is always false: 0 <= result.

V1029 Numeric Truncation Error. Return value of the 'size' function is written to the 16-bit variable.

V1029 Numeric Truncation Error. Return value of the 'size' function is written to the 16-bit variable.