1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
3  * This file is part of the LibreOffice project.
4  *
5  * This Source Code Form is subject to the terms of the Mozilla Public
6  * License, v. 2.0. If a copy of the MPL was not distributed with this
7  * file, You can obtain one at http://mozilla.org/MPL/2.0/.
8  *
9  * This file incorporates work covered by the following license notice:
10  *
11  *   Licensed to the Apache Software Foundation (ASF) under one or more
12  *   contributor license agreements. See the NOTICE file distributed
13  *   with this work for additional information regarding copyright
14  *   ownership. The ASF licenses this file to you under the Apache
15  *   License, Version 2.0 (the "License"); you may not use this file
16  *   except in compliance with the License. You may obtain a copy of
17  *   the License at http://www.apache.org/licenses/LICENSE-2.0 .
18  */
19 
20 #include <memory>
21 #include "docxattributeoutput.hxx"
22 #include "docxhelper.hxx"
23 #include "docxsdrexport.hxx"
24 #include "docxexportfilter.hxx"
25 #include "docxfootnotes.hxx"
26 #include "writerwordglue.hxx"
27 #include "ww8par.hxx"
28 #include <fmtcntnt.hxx>
29 #include <fmtftn.hxx>
30 #include <fchrfmt.hxx>
31 #include <tgrditem.hxx>
32 #include <fmtruby.hxx>
33 #include <fmtfollowtextflow.hxx>
34 #include <fmtanchr.hxx>
35 #include <breakit.hxx>
36 #include <redline.hxx>
37 #include <unoframe.hxx>
38 #include <textboxhelper.hxx>
39 #include <rdfhelper.hxx>
40 #include "wrtww8.hxx"
41 
42 #include <comphelper/processfactory.hxx>
43 #include <comphelper/random.hxx>
44 #include <comphelper/string.hxx>
45 #include <comphelper/flagguard.hxx>
46 #include <comphelper/sequence.hxx>
47 #include <oox/token/namespaces.hxx>
48 #include <oox/token/tokens.hxx>
49 #include <oox/export/utils.hxx>
50 #include <oox/mathml/imexport.hxx>
51 #include <oox/drawingml/drawingmltypes.hxx>
52 #include <oox/token/relationship.hxx>
53 #include <oox/export/vmlexport.hxx>
54 #include <oox/ole/olehelper.hxx>
55 #include <oox/export/drawingml.hxx>
56 
57 #include <editeng/autokernitem.hxx>
58 #include <editeng/unoprnms.hxx>
59 #include <editeng/fontitem.hxx>
60 #include <editeng/tstpitem.hxx>
61 #include <editeng/spltitem.hxx>
62 #include <editeng/widwitem.hxx>
63 #include <editeng/shaditem.hxx>
64 #include <editeng/brushitem.hxx>
65 #include <editeng/postitem.hxx>
66 #include <editeng/wghtitem.hxx>
67 #include <editeng/kernitem.hxx>
68 #include <editeng/crossedoutitem.hxx>
69 #include <editeng/cmapitem.hxx>
70 #include <editeng/udlnitem.hxx>
71 #include <editeng/langitem.hxx>
72 #include <editeng/lspcitem.hxx>
73 #include <editeng/escapementitem.hxx>
74 #include <editeng/fhgtitem.hxx>
75 #include <editeng/colritem.hxx>
76 #include <editeng/hyphenzoneitem.hxx>
77 #include <editeng/ulspitem.hxx>
78 #include <editeng/contouritem.hxx>
79 #include <editeng/shdditem.hxx>
80 #include <editeng/emphasismarkitem.hxx>
81 #include <editeng/twolinesitem.hxx>
82 #include <editeng/charscaleitem.hxx>
83 #include <editeng/charrotateitem.hxx>
84 #include <editeng/charreliefitem.hxx>
85 #include <editeng/paravertalignitem.hxx>
86 #include <editeng/pgrditem.hxx>
87 #include <editeng/frmdiritem.hxx>
88 #include <editeng/blinkitem.hxx>
89 #include <editeng/charhiddenitem.hxx>
90 #include <editeng/editobj.hxx>
91 #include <editeng/keepitem.hxx>
92 #include <editeng/borderline.hxx>
93 #include <sax/tools/converter.hxx>
94 #include <svx/xdef.hxx>
95 #include <svx/xfillit0.hxx>
96 #include <svx/xflclit.hxx>
97 #include <svx/xflgrit.hxx>
98 #include <svx/svdouno.hxx>
99 #include <svx/unobrushitemhelper.hxx>
100 #include <svl/grabbagitem.hxx>
101 #include <tools/date.hxx>
102 #include <tools/datetime.hxx>
103 #include <tools/datetimeutils.hxx>
104 #include <svl/whiter.hxx>
105 #include <rtl/tencinfo.h>
106 #include <sal/log.hxx>
107 #include <sot/exchange.hxx>
108 
109 #include <docufld.hxx>
110 #include <authfld.hxx>
111 #include <flddropdown.hxx>
112 #include <fmtclds.hxx>
113 #include <fmtinfmt.hxx>
114 #include <fmtline.hxx>
115 #include <ftninfo.hxx>
116 #include <htmltbl.hxx>
117 #include <lineinfo.hxx>
118 #include <ndgrf.hxx>
119 #include <ndole.hxx>
120 #include <ndtxt.hxx>
121 #include <pagedesc.hxx>
122 #include <paratr.hxx>
123 #include <swmodule.hxx>
124 #include <swtable.hxx>
125 #include <txtftn.hxx>
126 #include <fmtautofmt.hxx>
127 #include <docsh.hxx>
128 #include <docary.hxx>
129 #include <fmtclbl.hxx>
130 #include <fmtftntx.hxx>
131 #include <IDocumentSettingAccess.hxx>
132 #include <IDocumentRedlineAccess.hxx>
133 #include <grfatr.hxx>
134 #include <frmatr.hxx>
135 #include <txtatr.hxx>
136 #include <frameformats.hxx>
137 #include <textcontentcontrol.hxx>
138 #include <formatflysplit.hxx>
139 
140 #include <o3tl/string_view.hxx>
141 #include <o3tl/unit_conversion.hxx>
142 #include <osl/file.hxx>
143 #include <utility>
144 #include <vcl/embeddedfontshelper.hxx>
145 
146 #include <com/sun/star/i18n/ScriptType.hpp>
147 #include <com/sun/star/i18n/XBreakIterator.hpp>
148 #include <com/sun/star/chart2/XChartDocument.hpp>
149 #include <com/sun/star/drawing/ShadingPattern.hpp>
150 #include <com/sun/star/text/GraphicCrop.hpp>
151 #include <com/sun/star/embed/EmbedStates.hpp>
152 #include <com/sun/star/embed/Aspects.hpp>
153 #include <com/sun/star/text/ControlCharacter.hpp>
154 
155 #include <algorithm>
156 #include <cstddef>
157 #include <stdarg.h>
158 #include <string_view>
159 
160 #include <toolkit/helper/vclunohelper.hxx>
161 #include <unicode/regex.h>
162 #include <frozen/bits/defines.h>
163 #include <frozen/bits/elsa_std.h>
164 #include <frozen/unordered_map.h>
165 #include <IDocumentDeviceAccess.hxx>
166 #include <sfx2/printer.hxx>
167 
168 using ::editeng::SvxBorderLine;
169 
170 using namespace oox;
171 using namespace docx;
172 using namespace sax_fastparser;
173 using namespace nsSwDocInfoSubType;
174 using namespace sw::util;
175 using namespace ::com::sun::star;
176 using namespace ::com::sun::star::drawing;
177 
178 namespace {
179 
180 class FFDataWriterHelper
181 {
182     ::sax_fastparser::FSHelperPtr m_pSerializer;
writeCommonStart(const OUString & rName,const OUString & rEntryMacro,const OUString & rExitMacro,const OUString & rHelp,const OUString & rHint)183     void writeCommonStart( const OUString& rName,
184                            const OUString& rEntryMacro,
185                            const OUString& rExitMacro,
186                            const OUString& rHelp,
187                            const OUString& rHint )
188     {
189         m_pSerializer->startElementNS(XML_w, XML_ffData);
190         m_pSerializer->singleElementNS(XML_w, XML_name, FSNS(XML_w, XML_val), rName);
191         m_pSerializer->singleElementNS(XML_w, XML_enabled);
192         m_pSerializer->singleElementNS(XML_w, XML_calcOnExit, FSNS(XML_w, XML_val), "0");
193 
194         if ( !rEntryMacro.isEmpty() )
195             m_pSerializer->singleElementNS( XML_w, XML_entryMacro,
196                 FSNS(XML_w, XML_val), rEntryMacro );
197 
198         if ( !rExitMacro.isEmpty() )
199             m_pSerializer->singleElementNS(XML_w, XML_exitMacro, FSNS(XML_w, XML_val), rExitMacro);
200 
201         if ( !rHelp.isEmpty() )
202             m_pSerializer->singleElementNS( XML_w, XML_helpText,
203                 FSNS(XML_w, XML_type), "text",
204                 FSNS(XML_w, XML_val), rHelp );
205 
206         if ( !rHint.isEmpty() )
207             m_pSerializer->singleElementNS( XML_w, XML_statusText,
208                 FSNS(XML_w, XML_type), "text",
209                 FSNS(XML_w, XML_val), rHint );
210 
211     }
writeFinish()212     void writeFinish()
213     {
214         m_pSerializer->endElementNS( XML_w, XML_ffData );
215     }
216 public:
FFDataWriterHelper(::sax_fastparser::FSHelperPtr rSerializer)217     explicit FFDataWriterHelper( ::sax_fastparser::FSHelperPtr  rSerializer ) : m_pSerializer(std::move( rSerializer )){}
WriteFormCheckbox(const OUString & rName,const OUString & rEntryMacro,const OUString & rExitMacro,const OUString & rHelp,const OUString & rHint,bool bChecked)218     void WriteFormCheckbox( const OUString& rName,
219                             const OUString& rEntryMacro,
220                             const OUString& rExitMacro,
221                             const OUString& rHelp,
222                             const OUString& rHint,
223                             bool bChecked )
224     {
225         writeCommonStart( rName, rEntryMacro, rExitMacro, rHelp, rHint );
226         // Checkbox specific bits
227         m_pSerializer->startElementNS(XML_w, XML_checkBox);
228         // currently hardcoding autosize
229         // #TODO check if this defaulted
230         m_pSerializer->startElementNS(XML_w, XML_sizeAuto);
231         m_pSerializer->endElementNS( XML_w, XML_sizeAuto );
232         if ( bChecked )
233             m_pSerializer->singleElementNS(XML_w, XML_checked);
234         m_pSerializer->endElementNS( XML_w, XML_checkBox );
235         writeFinish();
236     }
237 
WriteFormText(const OUString & rName,const OUString & rEntryMacro,const OUString & rExitMacro,const OUString & rHelp,const OUString & rHint,const OUString & rType,const OUString & rDefaultText,sal_uInt16 nMaxLength,const OUString & rFormat)238     void WriteFormText(  const OUString& rName,
239                          const OUString& rEntryMacro,
240                          const OUString& rExitMacro,
241                          const OUString& rHelp,
242                          const OUString& rHint,
243                          const OUString& rType,
244                          const OUString& rDefaultText,
245                          sal_uInt16 nMaxLength,
246                          const OUString& rFormat )
247     {
248         writeCommonStart( rName, rEntryMacro, rExitMacro, rHelp, rHint );
249 
250         m_pSerializer->startElementNS(XML_w, XML_textInput);
251         if ( !rType.isEmpty() )
252             m_pSerializer->singleElementNS(XML_w, XML_type, FSNS(XML_w, XML_val), rType);
253         if ( !rDefaultText.isEmpty() )
254             m_pSerializer->singleElementNS(XML_w, XML_default, FSNS(XML_w, XML_val), rDefaultText);
255         if ( nMaxLength )
256             m_pSerializer->singleElementNS( XML_w, XML_maxLength,
257                 FSNS(XML_w, XML_val), OString::number(nMaxLength) );
258         if ( !rFormat.isEmpty() )
259             m_pSerializer->singleElementNS(XML_w, XML_format, FSNS(XML_w, XML_val), rFormat);
260         m_pSerializer->endElementNS( XML_w, XML_textInput );
261 
262         writeFinish();
263     }
264 };
265 
266 class FieldMarkParamsHelper
267 {
268     const sw::mark::IFieldmark& mrFieldmark;
269     public:
FieldMarkParamsHelper(const sw::mark::IFieldmark & rFieldmark)270     explicit FieldMarkParamsHelper( const sw::mark::IFieldmark& rFieldmark ) : mrFieldmark( rFieldmark ) {}
getName() const271     OUString const & getName() const { return mrFieldmark.GetName(); }
272     template < typename T >
extractParam(const OUString & rKey,T & rResult)273     bool extractParam( const OUString& rKey, T& rResult )
274     {
275         bool bResult = false;
276         if ( mrFieldmark.GetParameters() )
277         {
278             sw::mark::IFieldmark::parameter_map_t::const_iterator it = mrFieldmark.GetParameters()->find( rKey );
279             if ( it != mrFieldmark.GetParameters()->end() )
280                 bResult = ( it->second >>= rResult );
281         }
282         return bResult;
283     }
284 };
285 
286 // [ISO/IEC29500-1:2016] 17.18.50 ST_LongHexNumber (Eight Digit Hexadecimal Value)
NumberToHexBinary(sal_Int32 n)287 OUString NumberToHexBinary(sal_Int32 n)
288 {
289     OUStringBuffer aBuf;
290     sax::Converter::convertNumberToHexBinary(aBuf, n);
291     return aBuf.makeStringAndClear();
292 }
293 
294 // Returns a new reference with the previous content of src; src is empty after this
detachFrom(rtl::Reference<sax_fastparser::FastAttributeList> & src)295 auto detachFrom(rtl::Reference<sax_fastparser::FastAttributeList>& src)
296 {
297     return rtl::Reference(std::move(src));
298 }
299 
lclAddThemeValuesToCustomAttributes(rtl::Reference<sax_fastparser::FastAttributeList> & pAttrList,model::ComplexColor const & rComplexColor,sal_Int32 nThemeAttrId,sal_Int32 nThemeTintAttrId,sal_Int32 nThemeShadeAttrId)300 void lclAddThemeValuesToCustomAttributes(
301     rtl::Reference<sax_fastparser::FastAttributeList>& pAttrList, model::ComplexColor const& rComplexColor,
302     sal_Int32 nThemeAttrId, sal_Int32 nThemeTintAttrId, sal_Int32 nThemeShadeAttrId)
303 {
304     static constexpr auto constThemeColorTypeTokenMap = frozen::make_unordered_map<model::ThemeColorType, const char*>({
305         { model::ThemeColorType::Dark1, "dark1" },
306         { model::ThemeColorType::Light1, "light1" },
307         { model::ThemeColorType::Dark2, "dark2" },
308         { model::ThemeColorType::Light2, "light2" },
309         { model::ThemeColorType::Accent1, "accent1" },
310         { model::ThemeColorType::Accent2, "accent2" },
311         { model::ThemeColorType::Accent3, "accent3" },
312         { model::ThemeColorType::Accent4, "accent4" },
313         { model::ThemeColorType::Accent5, "accent5" },
314         { model::ThemeColorType::Accent6, "accent6" },
315         { model::ThemeColorType::Hyperlink, "hyperlink" },
316         { model::ThemeColorType::FollowedHyperlink, "followedHyperlink" }
317     });
318 
319     if (rComplexColor.isValidThemeType())
320     {
321         const auto iter = constThemeColorTypeTokenMap.find(rComplexColor.getThemeColorType());
322         assert(iter != constThemeColorTypeTokenMap.end());
323         OString sSchemeType = iter->second;
324         if (rComplexColor.getThemeColorUsage() == model::ThemeColorUsage::Text)
325         {
326             if (rComplexColor.getThemeColorType() == model::ThemeColorType::Dark1)
327                 sSchemeType = "text1"_ostr;
328             else if (rComplexColor.getThemeColorType() == model::ThemeColorType::Dark2)
329                 sSchemeType = "text2"_ostr;
330         }
331         else if (rComplexColor.getThemeColorUsage() == model::ThemeColorUsage::Background)
332         {
333             if (rComplexColor.getThemeColorType() == model::ThemeColorType::Light1)
334                 sSchemeType = "background1"_ostr;
335             else if (rComplexColor.getThemeColorType() == model::ThemeColorType::Light2)
336                 sSchemeType = "background2"_ostr;
337         }
338 
339         DocxAttributeOutput::AddToAttrList(pAttrList, FSNS(XML_w, nThemeAttrId), sSchemeType);
340 
341         sal_Int16 nLumMod = 10'000;
342         sal_Int16 nLumOff = 0;
343         sal_Int16 nTint = 0;
344         sal_Int16 nShade = 0;
345 
346         for (auto const& rTransform : rComplexColor.getTransformations())
347         {
348             if (rTransform.meType == model::TransformationType::LumMod)
349                 nLumMod = rTransform.mnValue;
350             if (rTransform.meType == model::TransformationType::LumOff)
351                 nLumOff = rTransform.mnValue;
352             if (rTransform.meType == model::TransformationType::Tint)
353                 nTint = rTransform.mnValue;
354             if (rTransform.meType == model::TransformationType::Shade)
355                 nShade = rTransform.mnValue;
356         }
357         if (nLumMod == 10'000 && nLumOff == 0)
358         {
359             if (nTint != 0)
360             {
361                 // Convert from 0-100 into 0-255
362                 sal_Int16 nTint255 = std::round(255.0 - (double(nTint) / 10000.0) * 255.0);
363                 DocxAttributeOutput::AddToAttrList(pAttrList, FSNS(XML_w, nThemeTintAttrId), OString::number(nTint255, 16));
364             }
365             else if (nShade != 0)
366             {
367                 // Convert from 0-100 into 0-255
368                 sal_Int16 nShade255 = std::round(255.0 - (double(nShade) / 10000.0) * 255.0);
369                 DocxAttributeOutput::AddToAttrList(pAttrList, FSNS(XML_w, nThemeShadeAttrId), OString::number(nShade255, 16));
370             }
371         }
372         else
373         {
374             double nPercentage = 0.0;
375 
376             if (nLumOff > 0)
377                 nPercentage = double(nLumOff) / 100.0;
378             else
379                 nPercentage = (-10'000 + double(nLumMod)) / 100.0;
380 
381             // Convert from 0-100 into 0-255
382             sal_Int16 nTintShade255 = std::round(255.0 - (std::abs(nPercentage) / 100.0) * 255.0);
383 
384             if (nPercentage > 0)
385                 DocxAttributeOutput::AddToAttrList(pAttrList, FSNS(XML_w, nThemeTintAttrId), OString::number(nTintShade255, 16));
386             else if (nPercentage < 0)
387                 DocxAttributeOutput::AddToAttrList(pAttrList, FSNS(XML_w, nThemeShadeAttrId), OString::number(nTintShade255, 16));
388         }
389     }
390 }
391 
lclAddThemeFillColorAttributes(rtl::Reference<sax_fastparser::FastAttributeList> & pAttrList,model::ComplexColor const & rComplexColor)392 void lclAddThemeFillColorAttributes(rtl::Reference<sax_fastparser::FastAttributeList>& pAttrList, model::ComplexColor const& rComplexColor)
393 {
394     lclAddThemeValuesToCustomAttributes(pAttrList, rComplexColor, XML_themeFill, XML_themeFillTint, XML_themeFillShade);
395 }
396 
lclAddThemeColorAttributes(rtl::Reference<sax_fastparser::FastAttributeList> & pAttrList,model::ComplexColor const & rComplexColor)397 void lclAddThemeColorAttributes(rtl::Reference<sax_fastparser::FastAttributeList>& pAttrList, model::ComplexColor const& rComplexColor)
398 {
399     lclAddThemeValuesToCustomAttributes(pAttrList, rComplexColor, XML_themeColor, XML_themeTint, XML_themeShade);
400 }
401 
402 } // end anonymous namespace
403 
RTLAndCJKState(bool bIsRTL,sal_uInt16)404 void DocxAttributeOutput::RTLAndCJKState( bool bIsRTL, sal_uInt16 /*nScript*/ )
405 {
406     if (bIsRTL)
407         m_pSerializer->singleElementNS(XML_w, XML_rtl, FSNS(XML_w, XML_val), "true");
408 }
409 
410 /// Are multiple paragraphs disallowed inside this type of SDT?
lcl_isOnelinerSdt(std::u16string_view rName)411 static bool lcl_isOnelinerSdt(std::u16string_view rName)
412 {
413     return rName == u"Title" || rName == u"Subtitle" || rName == u"Company";
414 }
415 
416 // write a floating table directly to docx without the surrounding frame
WriteFloatingTable(ww8::Frame const * pParentFrame)417 void DocxAttributeOutput::WriteFloatingTable(ww8::Frame const* pParentFrame)
418 {
419     const SwFrameFormat& rFrameFormat = pParentFrame->GetFrameFormat();
420     m_aFloatingTablesOfParagraph.insert(&rFrameFormat);
421     const SwNodeIndex* pNodeIndex = rFrameFormat.GetContent().GetContentIdx();
422 
423     SwNodeOffset nStt = pNodeIndex ? pNodeIndex->GetIndex() + 1 : SwNodeOffset(0);
424     SwNodeOffset nEnd = pNodeIndex ? pNodeIndex->GetNode().EndOfSectionIndex() : SwNodeOffset(0);
425 
426     //Save data here and restore when out of scope
427     ExportDataSaveRestore aDataGuard(GetExport(), nStt, nEnd, pParentFrame);
428 
429     // Stash away info about the current table, so m_tableReference is clean.
430     DocxTableExportContext aTableExportContext(*this);
431 
432     // set a floatingTableFrame AND unset parent frame,
433     // otherwise exporter thinks we are still in a frame
434     m_rExport.SetFloatingTableFrame(pParentFrame);
435     m_rExport.m_pParentFrame = nullptr;
436 
437     GetExport().WriteText();
438 
439     m_rExport.SetFloatingTableFrame(nullptr);
440 }
441 
checkAndWriteFloatingTables(DocxAttributeOutput & rDocxAttributeOutput)442 static void checkAndWriteFloatingTables(DocxAttributeOutput& rDocxAttributeOutput)
443 {
444     const auto& rExport = rDocxAttributeOutput.GetExport();
445     // iterate though all SpzFrameFormats and check whether they are anchored to the current text node
446     for( sal_uInt16 nCnt = rExport.m_rDoc.GetSpzFrameFormats()->size(); nCnt; )
447     {
448         const SwFrameFormat* pFrameFormat = (*rExport.m_rDoc.GetSpzFrameFormats())[ --nCnt ];
449         const SwFormatAnchor& rAnchor = pFrameFormat->GetAnchor();
450         const SwNode* pAnchorNode = rAnchor.GetAnchorNode();
451 
452         if (!pAnchorNode || ! rExport.m_pCurPam->GetPointNode().GetTextNode())
453             continue;
454 
455         if (*pAnchorNode != *rExport.m_pCurPam->GetPointNode().GetTextNode())
456             continue;
457 
458         const SwNodeIndex* pStartNode = pFrameFormat->GetContent().GetContentIdx();
459         if (!pStartNode)
460             continue;
461 
462         SwNodeIndex aStartNode = *pStartNode;
463 
464         // go to the next node (actual content)
465         ++aStartNode;
466 
467         // this has to be a table
468         if (!aStartNode.GetNode().IsTableNode())
469             continue;
470 
471         // go to the end of the table
472         SwNodeOffset aEndIndex = aStartNode.GetNode().EndOfSectionIndex();
473         // go one deeper
474         aEndIndex++;
475         // this has to be the end of the content
476         if (aEndIndex != pFrameFormat->GetContent().GetContentIdx()->GetNode().EndOfSectionIndex())
477             continue;
478 
479         // check for a grabBag and "TablePosition" attribute -> then we can export the table directly
480         SwTableNode* pTableNode = aStartNode.GetNode().GetTableNode();
481         SwTable& rTable = pTableNode->GetTable();
482         SwFrameFormat* pTableFormat = rTable.GetFrameFormat();
483         const SfxGrabBagItem* pTableGrabBag = pTableFormat->GetAttrSet().GetItem<SfxGrabBagItem>(RES_FRMATR_GRABBAG);
484         const std::map<OUString, css::uno::Any> & rTableGrabBag = pTableGrabBag->GetGrabBag();
485         // no grabbag?
486         if (rTableGrabBag.find(u"TablePosition"_ustr) == rTableGrabBag.end())
487         {
488             if (pFrameFormat->GetFlySplit().GetValue())
489             {
490                 ww8::Frame aFrame(*pFrameFormat, *rAnchor.GetContentAnchor());
491                 rDocxAttributeOutput.WriteFloatingTable(&aFrame);
492             }
493             continue;
494         }
495 
496         // write table to docx
497         ww8::Frame aFrame(*pFrameFormat, *rAnchor.GetContentAnchor());
498         rDocxAttributeOutput.WriteFloatingTable(&aFrame);
499     }
500 }
501 
StartParagraph(ww8::WW8TableNodeInfo::Pointer_t pTextNodeInfo,bool bGenerateParaId)502 sal_Int32 DocxAttributeOutput::StartParagraph(ww8::WW8TableNodeInfo::Pointer_t pTextNodeInfo,
503                                               bool bGenerateParaId)
504 {
505     // Paragraphs (in headers/footers/comments/frames etc) can start before another finishes.
506     // So a stack is needed to keep track of each paragraph's status separately.
507     // Complication: Word can't handle nested text boxes, so those need to be collected together.
508     if ( !m_aFramesOfParagraph.size() || !m_nTextFrameLevel )
509         m_aFramesOfParagraph.push(std::vector<ww8::Frame>());
510 
511     if ( m_nColBreakStatus == COLBRK_POSTPONE )
512         m_nColBreakStatus = COLBRK_WRITE;
513 
514     // Output table/table row/table cell starts if needed
515     if ( pTextNodeInfo )
516     {
517         // New cell/row?
518         if ( m_tableReference.m_nTableDepth > 0 && !m_tableReference.m_bTableCellOpen )
519         {
520             ww8::WW8TableNodeInfoInner::Pointer_t pDeepInner( pTextNodeInfo->getInnerForDepth( m_tableReference.m_nTableDepth ) );
521             if ( pDeepInner->getCell() == 0 )
522                 StartTableRow( pDeepInner );
523 
524             const sal_uInt32 nCell = pDeepInner->getCell();
525             const sal_uInt32 nRow = pDeepInner->getRow();
526 
527             SyncNodelessCells(pDeepInner, nCell, nRow);
528             StartTableCell(pDeepInner, nCell, nRow);
529         }
530 
531         sal_uInt32 nRow = pTextNodeInfo->getRow();
532         sal_uInt32 nCell = pTextNodeInfo->getCell();
533         if (nCell == 0)
534         {
535             // Do we have to start the table?
536             // [If we are at the right depth already, it means that we
537             // continue the table cell]
538             sal_uInt32 nCurrentDepth = pTextNodeInfo->getDepth();
539 
540             if ( nCurrentDepth > m_tableReference.m_nTableDepth )
541             {
542                 // Start all the tables that begin here
543                 for ( sal_uInt32 nDepth = m_tableReference.m_nTableDepth + 1; nDepth <= nCurrentDepth; ++nDepth )
544                 {
545                     ww8::WW8TableNodeInfoInner::Pointer_t pInner( pTextNodeInfo->getInnerForDepth( nDepth ) );
546 
547                     StartTable( pInner );
548                     StartTableRow( pInner );
549 
550                     StartTableCell(pInner, 0, nDepth == nCurrentDepth ? nRow : 0);
551                 }
552 
553                 m_tableReference.m_nTableDepth = nCurrentDepth;
554             }
555         }
556     }
557 
558     // look ahead for floating tables that were put into a frame during import
559     // floating tables in shapes are not supported: exclude this case
560     if (!m_rExport.SdrExporter().IsDMLAndVMLDrawingOpen())
561     {
562         // Do this after opening table/row/cell, so floating tables anchored at cell start go inside
563         // the cell, not outside.
564         checkAndWriteFloatingTables(*this);
565     }
566 
567     // Look up the "sdt end before this paragraph" property early, when it
568     // would normally arrive, it would be too late (would be after the
569     // paragraph start has been written).
570     bool bEndParaSdt = false;
571     if (m_aParagraphSdt.m_bStartedSdt)
572     {
573         SwTextNode* pTextNode = m_rExport.m_pCurPam->GetPointNode().GetTextNode();
574         if (pTextNode && pTextNode->GetpSwAttrSet())
575         {
576             const SfxItemSet* pSet = pTextNode->GetpSwAttrSet();
577             if (const SfxPoolItem* pItem = pSet->GetItem(RES_PARATR_GRABBAG))
578             {
579                 const SfxGrabBagItem& rParaGrabBag = static_cast<const SfxGrabBagItem&>(*pItem);
580                 const std::map<OUString, css::uno::Any>& rMap = rParaGrabBag.GetGrabBag();
581                 bEndParaSdt = m_aParagraphSdt.m_bStartedSdt && rMap.contains(u"ParaSdtEndBefore"_ustr);
582             }
583         }
584     }
585     // TODO also avoid multiline paragraphs in those SDT types for shape text
586     bool bOneliner = m_aParagraphSdt.m_bStartedSdt && !m_rExport.SdrExporter().IsDMLAndVMLDrawingOpen() && lcl_isOnelinerSdt(m_aStartedParagraphSdtPrAlias);
587     if (bEndParaSdt || (m_aParagraphSdt.m_bStartedSdt && m_bHadSectPr) || bOneliner)
588     {
589         // This is the common case: "close sdt before the current paragraph" was requested by the next paragraph.
590         m_aParagraphSdt.EndSdtBlock(m_pSerializer);
591         m_aStartedParagraphSdtPrAlias.clear();
592     }
593     m_bHadSectPr = false;
594 
595     // this mark is used to be able to enclose the paragraph inside a sdr tag.
596     // We will only know if we have to do that later.
597     m_pSerializer->mark(Tag_StartParagraph_1);
598 
599     std::optional<OUString> aParaId;
600     sal_Int32 nParaId = 0;
601     if (bGenerateParaId)
602     {
603         nParaId = m_nNextParaId++;
604         aParaId = NumberToHexBinary(nParaId);
605     }
606     m_pSerializer->startElementNS(XML_w, XML_p, FSNS(XML_w14, XML_paraId), aParaId);
607 
608     // postpone the output of the run (we get it before the paragraph
609     // properties, but must write it after them)
610     m_pSerializer->mark(Tag_StartParagraph_2);
611 
612     // no section break in this paragraph yet; can be set in SectionBreak()
613     m_pSectionInfo.reset();
614 
615     m_bParagraphOpened = true;
616     m_bIsFirstParagraph = false;
617     m_nHyperLinkCount.push_back(0);
618 
619     return nParaId;
620 }
621 
convertToOOXMLVertOrient(sal_Int16 nOrient)622 OString DocxAttributeOutput::convertToOOXMLVertOrient(sal_Int16 nOrient)
623 {
624     switch( nOrient )
625     {
626         case text::VertOrientation::CENTER:
627         case text::VertOrientation::LINE_CENTER:
628             return "center"_ostr;
629         case text::VertOrientation::BOTTOM:
630             return "bottom"_ostr;
631         case text::VertOrientation::LINE_BOTTOM:
632             return "outside"_ostr;
633         case text::VertOrientation::TOP:
634             return "top"_ostr;
635         case text::VertOrientation::LINE_TOP:
636             return "inside"_ostr;
637         default:
638             return OString();
639     }
640 }
641 
convertToOOXMLHoriOrient(sal_Int16 nOrient,bool bIsPosToggle)642 OString DocxAttributeOutput::convertToOOXMLHoriOrient(sal_Int16 nOrient, bool bIsPosToggle)
643 {
644     switch( nOrient )
645     {
646         case text::HoriOrientation::LEFT:
647             return bIsPosToggle ? "inside" : "left";
648         case text::HoriOrientation::INSIDE:
649             return "inside"_ostr;
650         case text::HoriOrientation::RIGHT:
651             return bIsPosToggle ? "outside" : "right";
652         case text::HoriOrientation::OUTSIDE:
653             return "outside"_ostr;
654         case text::HoriOrientation::CENTER:
655         case text::HoriOrientation::FULL:
656             return "center"_ostr;
657         default:
658             return OString();
659     }
660 }
661 
convertToOOXMLVertOrientRel(sal_Int16 nOrientRel)662 OString DocxAttributeOutput::convertToOOXMLVertOrientRel(sal_Int16 nOrientRel)
663 {
664     switch (nOrientRel)
665     {
666         case text::RelOrientation::PAGE_PRINT_AREA:
667             return "margin"_ostr;
668         case text::RelOrientation::PAGE_FRAME:
669             return "page"_ostr;
670         case text::RelOrientation::FRAME:
671         case text::RelOrientation::TEXT_LINE:
672         default:
673             return "text"_ostr;
674     }
675 }
676 
convertToOOXMLHoriOrientRel(sal_Int16 nOrientRel)677 OString DocxAttributeOutput::convertToOOXMLHoriOrientRel(sal_Int16 nOrientRel)
678 {
679     switch (nOrientRel)
680     {
681         case text::RelOrientation::PAGE_PRINT_AREA:
682             return "margin"_ostr;
683         case text::RelOrientation::PAGE_FRAME:
684             return "page"_ostr;
685         case text::RelOrientation::CHAR:
686         case text::RelOrientation::PAGE_RIGHT:
687         case text::RelOrientation::FRAME:
688         default:
689             return "text"_ostr;
690     }
691 }
692 
SetFrame(ww8::Frame * pFrame,sal_Int32 nTableDepth)693 void FramePrHelper::SetFrame(ww8::Frame* pFrame, sal_Int32 nTableDepth)
694 {
695     assert(!pFrame || !m_pFrame);
696     m_pFrame = pFrame;
697     m_nTableDepth = nTableDepth;
698     if (m_pFrame)
699     {
700         m_bUseFrameBorders = true;
701         m_bUseFrameBackground = true;
702         m_bUseFrameTextDirection = true;
703     }
704 }
705 
UseFrameBorders(sal_Int32 nTableDepth)706 bool FramePrHelper::UseFrameBorders(sal_Int32 nTableDepth)
707 {
708     if (!m_pFrame || m_nTableDepth < nTableDepth)
709         return false;
710 
711     return m_bUseFrameBorders;
712 }
713 
UseFrameBackground()714 bool FramePrHelper::UseFrameBackground()
715 {
716     if (!m_pFrame)
717         return false;
718 
719     return m_bUseFrameBackground;
720 }
721 
UseFrameTextDirection(sal_Int32 nTableDepth)722 bool FramePrHelper::UseFrameTextDirection(sal_Int32 nTableDepth)
723 {
724     if (!m_pFrame || m_nTableDepth < nTableDepth)
725         return false;
726 
727     return m_bUseFrameTextDirection;
728 }
729 
DeleteAndResetTheLists()730 void SdtBlockHelper::DeleteAndResetTheLists()
731 {
732     if (m_pTokenChildren.is() )
733         m_pTokenChildren.clear();
734     if (m_pDataBindingAttrs.is() )
735         m_pDataBindingAttrs.clear();
736     if (m_pTextAttrs.is())
737         m_pTextAttrs.clear();
738     if (!m_aAlias.isEmpty())
739         m_aAlias.clear();
740     if (!m_aTag.isEmpty())
741         m_aTag.clear();
742     if (!m_aLock.isEmpty())
743         m_aLock.clear();
744     if (!m_aPlaceHolderDocPart.isEmpty())
745         m_aPlaceHolderDocPart.clear();
746     if (!m_aColor.isEmpty())
747         m_aColor.clear();
748     if (!m_aAppearance.isEmpty())
749         m_aAppearance.clear();
750     m_bShowingPlaceHolder = false;
751     m_nId = 0;
752     m_nTabIndex = 0;
753 }
754 
WriteSdtBlock(const::sax_fastparser::FSHelperPtr & pSerializer,bool bRunTextIsOn,bool bParagraphHasDrawing)755 void SdtBlockHelper::WriteSdtBlock(const ::sax_fastparser::FSHelperPtr& pSerializer, bool bRunTextIsOn, bool bParagraphHasDrawing)
756 {
757     if (m_nSdtPrToken <= 0 && !m_pDataBindingAttrs.is() && !m_nId)
758         return;
759 
760     // sdt start mark
761     pSerializer->mark(DocxAttributeOutput::Tag_WriteSdtBlock);
762 
763     pSerializer->startElementNS(XML_w, XML_sdt);
764 
765     // output sdt properties
766     pSerializer->startElementNS(XML_w, XML_sdtPr);
767 
768     if (m_nSdtPrToken > 0 && m_pTokenChildren.is())
769     {
770         if (!m_pTokenAttributes.is())
771             pSerializer->startElement(m_nSdtPrToken);
772         else
773         {
774             pSerializer->startElement(m_nSdtPrToken, detachFrom(m_pTokenAttributes));
775         }
776 
777         if (m_nSdtPrToken == FSNS(XML_w, XML_date) || m_nSdtPrToken == FSNS(XML_w, XML_docPartObj) || m_nSdtPrToken == FSNS(XML_w, XML_docPartList) || m_nSdtPrToken == FSNS(XML_w14, XML_checkbox)) {
778             const uno::Sequence<xml::FastAttribute> aChildren = m_pTokenChildren->getFastAttributes();
779             for (const auto& rChild : aChildren)
780                 pSerializer->singleElement(rChild.Token, FSNS(XML_w, XML_val), rChild.Value);
781         }
782 
783         pSerializer->endElement(m_nSdtPrToken);
784     }
785     else if ((m_nSdtPrToken > 0) && m_nSdtPrToken != FSNS(XML_w, XML_id) && !(bRunTextIsOn && bParagraphHasDrawing))
786     {
787         if (!m_pTokenAttributes.is())
788             pSerializer->singleElement(m_nSdtPrToken);
789         else
790         {
791             pSerializer->singleElement(m_nSdtPrToken, detachFrom(m_pTokenAttributes));
792         }
793     }
794 
795     WriteExtraParams(pSerializer);
796 
797     pSerializer->endElementNS(XML_w, XML_sdtPr);
798 
799     // sdt contents start tag
800     pSerializer->startElementNS(XML_w, XML_sdtContent);
801 
802     // prepend the tags since the sdt start mark before the paragraph
803     pSerializer->mergeTopMarks(DocxAttributeOutput::Tag_WriteSdtBlock, sax_fastparser::MergeMarks::PREPEND);
804 
805     // write the ending tags after the paragraph
806     m_bStartedSdt = true;
807 
808     // clear sdt status
809     m_nSdtPrToken = 0;
810     DeleteAndResetTheLists();
811 }
812 
WriteExtraParams(const::sax_fastparser::FSHelperPtr & pSerializer)813 void SdtBlockHelper::WriteExtraParams(const ::sax_fastparser::FSHelperPtr& pSerializer)
814 {
815     if (m_nId)
816     {
817         pSerializer->singleElementNS(XML_w, XML_id, FSNS(XML_w, XML_val), OString::number(m_nId));
818     }
819 
820     if (m_pDataBindingAttrs.is())
821     {
822         pSerializer->singleElementNS(XML_w, XML_dataBinding, detachFrom(m_pDataBindingAttrs));
823     }
824 
825     if (m_pTextAttrs.is())
826     {
827         pSerializer->singleElementNS(XML_w, XML_text, detachFrom(m_pTextAttrs));
828     }
829 
830     if (!m_aPlaceHolderDocPart.isEmpty())
831     {
832         pSerializer->startElementNS(XML_w, XML_placeholder);
833         pSerializer->singleElementNS(XML_w, XML_docPart, FSNS(XML_w, XML_val), m_aPlaceHolderDocPart);
834         pSerializer->endElementNS(XML_w, XML_placeholder);
835     }
836 
837     if (m_bShowingPlaceHolder)
838         pSerializer->singleElementNS(XML_w, XML_showingPlcHdr);
839 
840     if (!m_aColor.isEmpty())
841     {
842         pSerializer->singleElementNS(XML_w15, XML_color, FSNS(XML_w, XML_val), m_aColor);
843     }
844 
845     if (!m_aAppearance.isEmpty())
846     {
847         pSerializer->singleElementNS(XML_w15, XML_appearance, FSNS(XML_w15, XML_val), m_aAppearance);
848     }
849 
850     if (!m_aAlias.isEmpty())
851         pSerializer->singleElementNS(XML_w, XML_alias, FSNS(XML_w, XML_val), m_aAlias);
852 
853     if (!m_aTag.isEmpty())
854         pSerializer->singleElementNS(XML_w, XML_tag, FSNS(XML_w, XML_val), m_aTag);
855 
856     if (m_nTabIndex)
857         pSerializer->singleElementNS(XML_w, XML_tabIndex, FSNS(XML_w, XML_val),
858                                      OString::number(m_nTabIndex));
859 
860     if (!m_aLock.isEmpty())
861         pSerializer->singleElementNS(XML_w, XML_lock, FSNS(XML_w, XML_val), m_aLock);
862 }
863 
EndSdtBlock(const::sax_fastparser::FSHelperPtr & pSerializer)864 void SdtBlockHelper::EndSdtBlock(const ::sax_fastparser::FSHelperPtr& pSerializer)
865 {
866     pSerializer->endElementNS(XML_w, XML_sdtContent);
867     pSerializer->endElementNS(XML_w, XML_sdt);
868     m_bStartedSdt = false;
869 }
870 
GetSdtParamsFromGrabBag(const uno::Sequence<beans::PropertyValue> & aGrabBagSdt)871 void SdtBlockHelper::GetSdtParamsFromGrabBag(const uno::Sequence<beans::PropertyValue>& aGrabBagSdt)
872 {
873     for (const beans::PropertyValue& aPropertyValue : aGrabBagSdt)
874     {
875         if (aPropertyValue.Name == "ooxml:CT_SdtPr_checkbox")
876         {
877             m_nSdtPrToken = FSNS(XML_w14, XML_checkbox);
878             uno::Sequence<beans::PropertyValue> aGrabBag;
879             aPropertyValue.Value >>= aGrabBag;
880             for (const auto& rProp : aGrabBag)
881             {
882                 if (rProp.Name == "ooxml:CT_SdtCheckbox_checked")
883                     DocxAttributeOutput::AddToAttrList(m_pTokenChildren,
884                         FSNS(XML_w14, XML_checked), rProp.Value.get<OUString>());
885                 else if (rProp.Name == "ooxml:CT_SdtCheckbox_checkedState")
886                     DocxAttributeOutput::AddToAttrList(m_pTokenChildren,
887                         FSNS(XML_w14, XML_checkedState), rProp.Value.get<OUString>());
888                 else if (rProp.Name == "ooxml:CT_SdtCheckbox_uncheckedState")
889                     DocxAttributeOutput::AddToAttrList(m_pTokenChildren,
890                         FSNS(XML_w14, XML_uncheckedState), rProp.Value.get<OUString>());
891             }
892         }
893         else if (aPropertyValue.Name == "ooxml:CT_SdtPr_dataBinding" && !m_pDataBindingAttrs.is())
894         {
895             uno::Sequence<beans::PropertyValue> aGrabBag;
896             aPropertyValue.Value >>= aGrabBag;
897             for (const auto& rProp : aGrabBag)
898             {
899                 if (rProp.Name == "ooxml:CT_DataBinding_prefixMappings")
900                     DocxAttributeOutput::AddToAttrList( m_pDataBindingAttrs,
901                                     FSNS( XML_w, XML_prefixMappings ), rProp.Value.get<OUString>());
902                 else if (rProp.Name == "ooxml:CT_DataBinding_xpath")
903                     DocxAttributeOutput::AddToAttrList( m_pDataBindingAttrs,
904                                     FSNS( XML_w, XML_xpath ), rProp.Value.get<OUString>());
905                 else if (rProp.Name == "ooxml:CT_DataBinding_storeItemID")
906                     DocxAttributeOutput::AddToAttrList( m_pDataBindingAttrs,
907                                     FSNS( XML_w, XML_storeItemID ), rProp.Value.get<OUString>());
908             }
909         }
910         else if (aPropertyValue.Name == "ooxml:CT_SdtPr_text")
911         {
912             uno::Sequence<beans::PropertyValue> aGrabBag;
913             aPropertyValue.Value >>= aGrabBag;
914             if (aGrabBag.hasElements())
915             {
916                 for (const auto& rProp : aGrabBag)
917                 {
918                     if (rProp.Name == "ooxml:CT_SdtText_multiLine")
919                         DocxAttributeOutput::AddToAttrList(m_pTextAttrs,
920                             FSNS(XML_w, XML_multiLine), rProp.Value.get<OUString>());
921                 }
922             }
923             else
924             {
925                 // We still have w:text, but no attrs
926                 m_nSdtPrToken = FSNS(XML_w, XML_text);
927             }
928         }
929         else if (aPropertyValue.Name == "ooxml:CT_SdtPlaceholder_docPart")
930         {
931             uno::Sequence<beans::PropertyValue> aGrabBag;
932             aPropertyValue.Value >>= aGrabBag;
933             for (const auto& rProp : aGrabBag)
934             {
935                 if (rProp.Name == "ooxml:CT_SdtPlaceholder_docPart_val")
936                     m_aPlaceHolderDocPart = rProp.Value.get<OUString>();
937             }
938         }
939         else if (aPropertyValue.Name == "ooxml:CT_SdtPr_color")
940         {
941             uno::Sequence<beans::PropertyValue> aGrabBag;
942             aPropertyValue.Value >>= aGrabBag;
943             for (const auto& rProp : aGrabBag)
944             {
945                 if (rProp.Name == "ooxml:CT_SdtColor_val")
946                     m_aColor = rProp.Value.get<OUString>();
947             }
948         }
949         else if (aPropertyValue.Name == "ooxml:CT_SdtPr_appearance")
950         {
951             if (!(aPropertyValue.Value >>= m_aAppearance))
952                 SAL_WARN("sw.ww8", "DocxAttributeOutput::GrabBag: unexpected sdt appearance value");
953         }
954         else if (aPropertyValue.Name == "ooxml:CT_SdtPr_showingPlcHdr")
955         {
956             if (!(aPropertyValue.Value >>= m_bShowingPlaceHolder))
957                 SAL_WARN("sw.ww8", "DocxAttributeOutput::GrabBag: unexpected sdt ShowingPlcHdr");
958         }
959         else if (aPropertyValue.Name == "ooxml:CT_SdtPr_alias" && m_aAlias.isEmpty())
960         {
961             if (!(aPropertyValue.Value >>= m_aAlias))
962                 SAL_WARN("sw.ww8", "DocxAttributeOutput::GrabBag: unexpected sdt alias value");
963         }
964         else if (aPropertyValue.Name == "ooxml:CT_SdtPr_tag" && m_aTag.isEmpty())
965         {
966             if (!(aPropertyValue.Value >>= m_aTag))
967                 SAL_WARN("sw.ww8", "DocxAttributeOutput::GrabBag: unexpected sdt tag value");
968         }
969         else if (aPropertyValue.Name == "ooxml:CT_SdtPr_id")
970         {
971             if (!(aPropertyValue.Value >>= m_nId))
972                 SAL_WARN("sw.ww8", "DocxAttributeOutput::GrabBag: unexpected sdt id value");
973         }
974         else if (aPropertyValue.Name == "ooxml:CT_SdtPr_tabIndex" && !m_nTabIndex)
975         {
976             if (!(aPropertyValue.Value >>= m_nTabIndex))
977                 SAL_WARN("sw.ww8", "DocxAttributeOutput::GrabBag: unexpected sdt tabIndex value");
978         }
979         else if (aPropertyValue.Name == "ooxml:CT_SdtPr_lock" && m_aLock.isEmpty())
980         {
981             if (!(aPropertyValue.Value >>= m_aLock))
982                 SAL_WARN("sw.ww8", "DocxAttributeOutput::GrabBag: unexpected sdt lock value");
983         }
984         else if (aPropertyValue.Name == "ooxml:CT_SdtPr_citation")
985             m_nSdtPrToken = FSNS(XML_w, XML_citation);
986         else if (aPropertyValue.Name == "ooxml:CT_SdtPr_docPartObj" ||
987             aPropertyValue.Name == "ooxml:CT_SdtPr_docPartList")
988         {
989             if (aPropertyValue.Name == "ooxml:CT_SdtPr_docPartObj")
990                 m_nSdtPrToken = FSNS(XML_w, XML_docPartObj);
991             else if (aPropertyValue.Name == "ooxml:CT_SdtPr_docPartList")
992                 m_nSdtPrToken = FSNS(XML_w, XML_docPartList);
993 
994             uno::Sequence<beans::PropertyValue> aGrabBag;
995             aPropertyValue.Value >>= aGrabBag;
996             for (const auto& rProp : aGrabBag)
997             {
998                 if (rProp.Name == "ooxml:CT_SdtDocPart_docPartGallery")
999                     DocxAttributeOutput::AddToAttrList(m_pTokenChildren,
1000                         FSNS(XML_w, XML_docPartGallery), rProp.Value.get<OUString>());
1001                 else if (rProp.Name == "ooxml:CT_SdtDocPart_docPartCategory")
1002                     DocxAttributeOutput::AddToAttrList(m_pTokenChildren,
1003                         FSNS(XML_w, XML_docPartCategory), rProp.Value.get<OUString>());
1004                 else if (rProp.Name == "ooxml:CT_SdtDocPart_docPartUnique")
1005                 {
1006                     OUString sValue = rProp.Value.get<OUString>();
1007                     if (sValue.isEmpty())
1008                         sValue = "true";
1009                     DocxAttributeOutput::AddToAttrList(m_pTokenChildren, FSNS(XML_w, XML_docPartUnique),
1010                         sValue);
1011                 }
1012             }
1013         }
1014         else if (aPropertyValue.Name == "ooxml:CT_SdtPr_equation")
1015             m_nSdtPrToken = FSNS(XML_w, XML_equation);
1016         else if (aPropertyValue.Name == "ooxml:CT_SdtPr_picture")
1017             m_nSdtPrToken = FSNS(XML_w, XML_picture);
1018         else if (aPropertyValue.Name == "ooxml:CT_SdtPr_group")
1019             m_nSdtPrToken = FSNS(XML_w, XML_group);
1020         else
1021             SAL_WARN("sw.ww8", "GetSdtParamsFromGrabBag unhandled SdtPr grab bag property " << aPropertyValue.Name);
1022     }
1023 }
1024 
PopulateFrameProperties(const SwFrameFormat * pFrameFormat,const Size & rSize)1025 void DocxAttributeOutput::PopulateFrameProperties(const SwFrameFormat* pFrameFormat, const Size& rSize)
1026 {
1027     rtl::Reference<sax_fastparser::FastAttributeList> attrList = FastSerializerHelper::createAttrList();
1028 
1029     const SwFormatHoriOrient& rHoriOrient = pFrameFormat->GetHoriOrient();
1030     const SwFormatVertOrient& rVertOrient = pFrameFormat->GetVertOrient();
1031     awt::Point aPos(rHoriOrient.GetPos(), rVertOrient.GetPos());
1032 
1033     // A few assumptions need to be made here, because framePr is a confused mixture
1034     // of (multiple) paragraph's border properties being transferred to/from a frame.
1035     // The frame size describes the size BEFORE the PARAGRAPH border spacing is applied.
1036     // However, we can't actually look at all the paragraphs' borders because they might be
1037     // different, and all MUST specify the same frame width in order to belong to the same frame.
1038     // In order for them all to be consistent, the only choice is to use the frame's border spacing.
1039     // During import, the frame was assigned border spacing based on the contained paragraphs.
1040     // So now at export time we have to assume that none of this has been changed by the user.
1041 
1042     // 620 (31pt) is the maximum paragraph border spacing allowed in MS Formats,
1043     // so if the value is greater than that, avoid adjusting the size - the user has interfered.
1044     const sal_uInt32 nLeftBorderSpacing = pFrameFormat->GetBox().GetDistance(SvxBoxItemLine::LEFT);
1045     const sal_uInt32 nRighttBorderSpacing = pFrameFormat->GetBox().GetDistance(SvxBoxItemLine::RIGHT);
1046     sal_uInt32 nAdjustedWidth = rSize.Width();
1047     if (nLeftBorderSpacing < 621 && nRighttBorderSpacing < 621
1048         && nAdjustedWidth > nLeftBorderSpacing + nRighttBorderSpacing)
1049     {
1050         nAdjustedWidth -= nLeftBorderSpacing + nRighttBorderSpacing;
1051     }
1052     attrList->add( FSNS( XML_w, XML_w), OString::number(nAdjustedWidth));
1053     attrList->add( FSNS( XML_w, XML_h), OString::number(rSize.Height()));
1054 
1055     const OString relativeFromH = convertToOOXMLHoriOrientRel(rHoriOrient.GetRelationOrient());
1056     const OString relativeFromV = convertToOOXMLVertOrientRel(rVertOrient.GetRelationOrient());
1057     OString aXAlign = convertToOOXMLHoriOrient(rHoriOrient.GetHoriOrient(), /*bIsPosToggle=*/false);
1058     OString aYAlign = convertToOOXMLVertOrient(rVertOrient.GetVertOrient());
1059     if (!aXAlign.isEmpty())
1060         attrList->add(FSNS(XML_w, XML_xAlign), aXAlign);
1061     else if (aPos.X)
1062         attrList->add( FSNS( XML_w, XML_x), OString::number(aPos.X));
1063     if (!aYAlign.isEmpty() && relativeFromV != "text")
1064         attrList->add(FSNS(XML_w, XML_yAlign), aYAlign);
1065     else if (aPos.Y)
1066         attrList->add( FSNS( XML_w, XML_y), OString::number(aPos.Y));
1067 
1068     sal_Int16 nLeft = pFrameFormat->GetLRSpace().GetLeft();
1069     sal_Int16 nRight = pFrameFormat->GetLRSpace().GetRight();
1070     sal_Int16 nUpper = pFrameFormat->GetULSpace().GetUpper();
1071     sal_Int16 nLower = pFrameFormat->GetULSpace().GetLower();
1072 
1073     // To emulate, on import left was ignored (set to zero) if aligned to left,
1074     // so just double up the right spacing in order to prevent cutting in half each round-trip.
1075     if (rHoriOrient.GetHoriOrient() == text::HoriOrientation::LEFT)
1076         nLeft = nRight;
1077     else if (rHoriOrient.GetHoriOrient() == text::HoriOrientation::RIGHT)
1078         nRight = nLeft;
1079 
1080     attrList->add(FSNS(XML_w, XML_hSpace), OString::number((nLeft + nRight) / 2));
1081     attrList->add(FSNS(XML_w, XML_vSpace), OString::number((nUpper + nLower) / 2));
1082 
1083     switch (pFrameFormat->GetSurround().GetValue())
1084     {
1085     case css::text::WrapTextMode_NONE:
1086         attrList->add( FSNS( XML_w, XML_wrap), "notBeside");
1087         break;
1088     case css::text::WrapTextMode_DYNAMIC:
1089         attrList->add(FSNS(XML_w, XML_wrap), "auto");
1090         break;
1091     case css::text::WrapTextMode_PARALLEL:
1092     default:
1093         attrList->add(FSNS(XML_w, XML_wrap), "around");
1094         break;
1095     }
1096     attrList->add( FSNS( XML_w, XML_vAnchor), relativeFromV );
1097     attrList->add( FSNS( XML_w, XML_hAnchor), relativeFromH );
1098     attrList->add( FSNS( XML_w, XML_hRule), "exact");
1099 
1100     m_pSerializer->singleElementNS( XML_w, XML_framePr, attrList );
1101 }
1102 
TextBoxIsFramePr(const SwFrameFormat & rFrameFormat)1103 bool DocxAttributeOutput::TextBoxIsFramePr(const SwFrameFormat& rFrameFormat)
1104 {
1105     SdrObject* pSdrObj = const_cast<SdrObject*>(rFrameFormat.FindRealSdrObject());
1106     if (!pSdrObj)
1107         return false;
1108 
1109     uno::Reference<beans::XPropertySet> xPropertySet(pSdrObj->getUnoShape(), uno::UNO_QUERY);
1110     if (!xPropertySet.is())
1111         return false;
1112 
1113     uno::Reference<beans::XPropertySetInfo> xPropSetInfo(xPropertySet->getPropertySetInfo());
1114     if (!xPropSetInfo.is() || !xPropSetInfo->hasPropertyByName(u"FrameInteropGrabBag"_ustr))
1115         return false;
1116 
1117     bool bRet = false;
1118     uno::Sequence<beans::PropertyValue> propList;
1119     xPropertySet->getPropertyValue(u"FrameInteropGrabBag"_ustr) >>= propList;
1120     auto pProp = std::find_if(std::cbegin(propList), std::cend(propList),
1121         [](const beans::PropertyValue& rProp) { return rProp.Name == "ParaFrameProperties"; });
1122     if (pProp != std::cend(propList))
1123         pProp->Value >>= bRet;
1124 
1125     return bRet;
1126 }
1127 
EndParagraph(ww8::WW8TableNodeInfoInner::Pointer_t pTextNodeInfoInner)1128 void DocxAttributeOutput::EndParagraph( ww8::WW8TableNodeInfoInner::Pointer_t pTextNodeInfoInner )
1129 {
1130     // write the paragraph properties + the run, already in the correct order
1131     m_pSerializer->mergeTopMarks(Tag_StartParagraph_2);
1132     std::vector<  std::shared_ptr <ww8::Frame> > aFramePrTextbox;
1133     // Write the anchored frame if any
1134     // Word can't handle nested text boxes, so write them on the same level.
1135     ++m_nTextFrameLevel;
1136     if( m_nTextFrameLevel == 1 && !m_rExport.SdrExporter().IsDMLAndVMLDrawingOpen() )
1137     {
1138         comphelper::FlagRestorationGuard aStartedParaSdtGuard(m_aParagraphSdt.m_bStartedSdt, false);
1139 
1140         assert(!m_oPostponedCustomShape);
1141         m_oPostponedCustomShape.emplace();
1142 
1143         // The for loop can change the size of m_aFramesOfParagraph, so the max size cannot be set in stone before the loop.
1144         size_t nFrames = m_aFramesOfParagraph.size() ? m_aFramesOfParagraph.top().size() : 0;
1145         for (size_t nIndex = 0; nIndex < nFrames; ++nIndex)
1146         {
1147             m_bParagraphFrameOpen = true;
1148             ww8::Frame aFrame = m_aFramesOfParagraph.top()[nIndex];
1149             const SwFrameFormat& rFrameFormat = aFrame.GetFrameFormat();
1150 
1151             if (!m_bWritingHeaderFooter && TextBoxIsFramePr(rFrameFormat))
1152             {
1153                 std::shared_ptr<ww8::Frame> pFramePr = std::make_shared<ww8::Frame>(aFrame);
1154                 aFramePrTextbox.push_back(pFramePr);
1155             }
1156             else
1157             {
1158                 if (m_aRunSdt.m_bStartedSdt)
1159                 {
1160                     // Run-level SDT still open? Close it before AlternateContent.
1161                     m_aRunSdt.EndSdtBlock(m_pSerializer);
1162                 }
1163                 m_pSerializer->startElementNS(XML_w, XML_r);
1164                 m_pSerializer->startElementNS(XML_mc, XML_AlternateContent);
1165                 m_pSerializer->startElementNS(XML_mc, XML_Choice, XML_Requires, "wps");
1166                 /**
1167                     This is to avoid AlternateContent within another AlternateContent.
1168                        So when Choice is Open, only write the DML Drawing instead of both DML
1169                        and VML Drawing in another AlternateContent.
1170                  **/
1171                 SetAlternateContentChoiceOpen( true );
1172                 /** Save the table info's before writing the shape
1173                         as there might be a new table that might get
1174                         spawned from within the VML & DML block and alter
1175                         the contents.
1176                 */
1177                 ww8::WW8TableInfo::Pointer_t xOldTableInfo = m_rExport.m_pTableInfo;
1178                 //Reset the table infos after saving.
1179                 m_rExport.m_pTableInfo = std::make_shared<ww8::WW8TableInfo>();
1180 
1181                 /** FDO#71834 :
1182                        Save the table reference attributes before calling WriteDMLTextFrame,
1183                        otherwise the StartParagraph function will use the previous existing
1184                        table reference attributes since the variable is being shared.
1185                 */
1186                 {
1187                     DocxTableExportContext aDMLTableExportContext(*this);
1188                     m_rExport.SdrExporter().writeDMLTextFrame(&aFrame, m_anchorId++);
1189                 }
1190                 m_pSerializer->endElementNS(XML_mc, XML_Choice);
1191                 SetAlternateContentChoiceOpen( false );
1192 
1193                 // Reset table infos, otherwise the depth of the cells will be incorrect,
1194                 // in case the text frame had table(s) and we try to export the
1195                 // same table second time.
1196                 m_rExport.m_pTableInfo = std::make_shared<ww8::WW8TableInfo>();
1197                 //reset the tableReference.
1198 
1199                 m_pSerializer->startElementNS(XML_mc, XML_Fallback);
1200                 {
1201                     DocxTableExportContext aVMLTableExportContext(*this);
1202                     m_rExport.SdrExporter().writeVMLTextFrame(&aFrame);
1203                 }
1204                 m_rExport.m_pTableInfo = std::move(xOldTableInfo);
1205 
1206                 m_pSerializer->endElementNS(XML_mc, XML_Fallback);
1207                 m_pSerializer->endElementNS(XML_mc, XML_AlternateContent);
1208                 m_pSerializer->endElementNS( XML_w, XML_r );
1209                 m_bParagraphFrameOpen = false;
1210             }
1211 
1212             nFrames = m_aFramesOfParagraph.size() ? m_aFramesOfParagraph.top().size() : 0;
1213         }
1214         if (!m_oPostponedCustomShape->empty())
1215         {
1216             m_pSerializer->startElementNS(XML_w, XML_r);
1217             WritePostponedCustomShape();
1218             m_pSerializer->endElementNS( XML_w, XML_r );
1219         }
1220         m_oPostponedCustomShape.reset();
1221 
1222         if ( m_aFramesOfParagraph.size() )
1223             m_aFramesOfParagraph.top().clear();
1224 
1225         if (!pTextNodeInfoInner)
1226         {
1227             // Ending a non-table paragraph, clear floating tables before paragraph.
1228             m_aFloatingTablesOfParagraph.clear();
1229         }
1230     }
1231 
1232     --m_nTextFrameLevel;
1233     if ( m_aFramesOfParagraph.size() && !m_nTextFrameLevel )
1234         m_aFramesOfParagraph.pop();
1235 
1236     /* If m_nHyperLinkCount > 0 that means hyperlink tag is not yet closed.
1237      * This is due to nested hyperlink tags. So close it before end of paragraph.
1238      */
1239     if(m_nHyperLinkCount.back() > 0)
1240     {
1241         for(sal_Int32 nHyperLinkToClose = 0; nHyperLinkToClose < m_nHyperLinkCount.back(); ++nHyperLinkToClose)
1242             m_pSerializer->endElementNS( XML_w, XML_hyperlink );
1243     }
1244     m_nHyperLinkCount.pop_back();
1245 
1246     if (m_aRunSdt.m_bStartedSdt)
1247     {
1248         // Run-level SDT still open? Close it now.
1249         m_aRunSdt.EndSdtBlock(m_pSerializer);
1250     }
1251 
1252     if (m_bPageBreakAfter)
1253     {
1254         // tdf#128889 Trailing page break
1255         SectionBreak(msword::PageBreak, false);
1256         m_bPageBreakAfter = false;
1257     }
1258 
1259     m_pSerializer->endElementNS( XML_w, XML_p );
1260     // on export sdt blocks are never nested ATM
1261     if (!m_bAnchorLinkedToNode && !m_aParagraphSdt.m_bStartedSdt)
1262     {
1263         m_aParagraphSdt.WriteSdtBlock(m_pSerializer, m_bRunTextIsOn, m_rExport.SdrExporter().IsParagraphHasDrawing());
1264 
1265         if (m_aParagraphSdt.m_bStartedSdt)
1266         {
1267             if (m_tableReference.m_bTableCellOpen)
1268                 m_tableReference.m_bTableCellParaSdtOpen = true;
1269             if (m_rExport.SdrExporter().IsDMLAndVMLDrawingOpen())
1270                 m_rExport.SdrExporter().setParagraphSdtOpen(true);
1271         }
1272     }
1273     else
1274     {
1275         //These should be written out to the actual Node and not to the anchor.
1276         //Clear them as they will be repopulated when the node is processed.
1277         m_aParagraphSdt.m_nSdtPrToken = 0;
1278         m_aParagraphSdt.DeleteAndResetTheLists();
1279     }
1280 
1281     m_pSerializer->mark(Tag_StartParagraph_2);
1282 
1283     // Write framePr
1284     for ( const auto & pFrame : aFramePrTextbox )
1285     {
1286         DocxTableExportContext aTableExportContext(*this);
1287         m_aFramePr.SetFrame(pFrame.get(), !m_xTableWrt ? -1 : m_tableReference.m_nTableDepth);
1288         m_rExport.SdrExporter().writeOnlyTextOfFrame(pFrame.get());
1289         m_aFramePr.SetFrame(nullptr);
1290     }
1291 
1292     m_pSerializer->mergeTopMarks(Tag_StartParagraph_2, sax_fastparser::MergeMarks::PREPEND);
1293 
1294     //sdtcontent is written so Set m_bParagraphHasDrawing to false
1295     m_rExport.SdrExporter().setParagraphHasDrawing(false);
1296     m_bRunTextIsOn = false;
1297     m_pSerializer->mergeTopMarks(Tag_StartParagraph_1);
1298 
1299     aFramePrTextbox.clear();
1300     // Check for end of cell, rows, tables here
1301     FinishTableRowCell( pTextNodeInfoInner );
1302 
1303     if( !m_rExport.SdrExporter().IsDMLAndVMLDrawingOpen() )
1304         m_bParagraphOpened = false;
1305 
1306     // Clear bookmarks of the current paragraph
1307     m_aBookmarksOfParagraphStart.clear();
1308     m_aBookmarksOfParagraphEnd.clear();
1309 }
1310 
1311 #define MAX_CELL_IN_WORD 62
1312 
SyncNodelessCells(ww8::WW8TableNodeInfoInner::Pointer_t const & pInner,sal_Int32 nCell,sal_uInt32 nRow)1313 void DocxAttributeOutput::SyncNodelessCells(ww8::WW8TableNodeInfoInner::Pointer_t const & pInner, sal_Int32 nCell, sal_uInt32 nRow)
1314 {
1315     sal_Int32 nOpenCell = m_LastOpenCell.back();
1316     if (nOpenCell != -1 && nOpenCell != nCell && nOpenCell < MAX_CELL_IN_WORD)
1317         EndTableCell(nOpenCell);
1318 
1319     sal_Int32 nClosedCell = m_LastClosedCell.back();
1320     for (sal_Int32 i = nClosedCell+1; i < nCell; ++i)
1321     {
1322         if (i >= MAX_CELL_IN_WORD)
1323             break;
1324 
1325         if (i == 0)
1326             StartTableRow(pInner);
1327 
1328         StartTableCell(pInner, i, nRow);
1329         m_pSerializer->singleElementNS(XML_w, XML_p);
1330         EndTableCell(i);
1331     }
1332 }
1333 
FinishTableRowCell(ww8::WW8TableNodeInfoInner::Pointer_t const & pInner,bool bForceEmptyParagraph)1334 void DocxAttributeOutput::FinishTableRowCell( ww8::WW8TableNodeInfoInner::Pointer_t const & pInner, bool bForceEmptyParagraph )
1335 {
1336     if ( !pInner )
1337         return;
1338 
1339     // Where are we in the table
1340     sal_uInt32 nRow = pInner->getRow();
1341     sal_Int32 nCell = pInner->getCell();
1342 
1343     InitTableHelper( pInner );
1344 
1345     // HACK
1346     // msoffice seems to have an internal limitation of 63 columns for tables
1347     // and refuses to load .docx with more, even though the spec seems to allow that;
1348     // so simply if there are more columns, don't close the last one msoffice will handle
1349     // and merge the contents of the remaining ones into it (since we don't close the cell
1350     // here, following ones will not be opened)
1351     const bool limitWorkaround = (nCell >= MAX_CELL_IN_WORD && !pInner->isEndOfLine());
1352     const bool bEndCell = pInner->isEndOfCell() && !limitWorkaround;
1353     const bool bEndRow = pInner->isEndOfLine();
1354 
1355     if (bEndCell)
1356     {
1357         while (pInner->getDepth() < m_tableReference.m_nTableDepth)
1358         {
1359             //we expect that the higher depth row was closed, and
1360             //we are just missing the table close
1361             assert(m_LastOpenCell.back() == -1 && m_LastClosedCell.back() == -1);
1362             EndTable();
1363         }
1364 
1365         SyncNodelessCells(pInner, nCell, nRow);
1366 
1367         sal_Int32 nClosedCell = m_LastClosedCell.back();
1368         if (nCell == nClosedCell)
1369         {
1370             //Start missing trailing cell(s)
1371             ++nCell;
1372             StartTableCell(pInner, nCell, nRow);
1373 
1374             //Continue on missing next trailing cell(s)
1375             ww8::RowSpansPtr xRowSpans = pInner->getRowSpansOfRow();
1376             sal_Int32 nRemainingCells = xRowSpans->size() - nCell;
1377             for (sal_Int32 i = 1; i < nRemainingCells; ++i)
1378             {
1379                 if (bForceEmptyParagraph)
1380                 {
1381                     m_pSerializer->singleElementNS(XML_w, XML_p);
1382                 }
1383 
1384                 EndTableCell(nCell);
1385 
1386                 StartTableCell(pInner, nCell, nRow);
1387             }
1388         }
1389 
1390         if (bForceEmptyParagraph)
1391         {
1392             m_pSerializer->singleElementNS(XML_w, XML_p);
1393         }
1394 
1395         EndTableCell(nCell);
1396     }
1397 
1398     // This is a line end
1399     if (bEndRow)
1400         EndTableRow();
1401 
1402     // This is the end of the table
1403     if (pInner->isFinalEndOfLine())
1404         EndTable();
1405 }
1406 
EmptyParagraph()1407 void DocxAttributeOutput::EmptyParagraph()
1408 {
1409     m_pSerializer->singleElementNS(XML_w, XML_p);
1410 }
1411 
SectionBreaks(const SwNode & rNode)1412 void DocxAttributeOutput::SectionBreaks(const SwNode& rNode)
1413 {
1414     // output page/section breaks
1415     // Writer can have them at the beginning of a paragraph, or at the end, but
1416     // in docx, we have to output them in the paragraph properties of the last
1417     // paragraph in a section.  To get it right, we have to switch to the next
1418     // paragraph, and detect the section breaks there.
1419     SwNodeIndex aNextIndex( rNode, 1 );
1420 
1421     if (rNode.IsTextNode() || rNode.IsSectionNode())
1422     {
1423         if (aNextIndex.GetNode().IsTextNode())
1424         {
1425             const SwTextNode* pTextNode = static_cast<SwTextNode*>(&aNextIndex.GetNode());
1426             m_rExport.OutputSectionBreaks(pTextNode->GetpSwAttrSet(), *pTextNode, m_tableReference.m_bTableCellOpen);
1427         }
1428         else if (aNextIndex.GetNode().IsTableNode())
1429         {
1430             const SwTableNode* pTableNode = static_cast<SwTableNode*>(&aNextIndex.GetNode());
1431             const SwFrameFormat *pFormat = pTableNode->GetTable().GetFrameFormat();
1432             m_rExport.OutputSectionBreaks(&(pFormat->GetAttrSet()), *pTableNode);
1433         }
1434     }
1435     else if (rNode.IsEndNode())
1436     {
1437         if (aNextIndex.GetNode().IsTextNode())
1438         {
1439             // Handle section break between a table and a text node following it.
1440             // Also handle section endings
1441             const SwTextNode* pTextNode = aNextIndex.GetNode().GetTextNode();
1442             if (rNode.StartOfSectionNode()->IsTableNode() || rNode.StartOfSectionNode()->IsSectionNode())
1443                 m_rExport.OutputSectionBreaks(pTextNode->GetpSwAttrSet(), *pTextNode, m_tableReference.m_bTableCellOpen);
1444         }
1445         else if (aNextIndex.GetNode().IsTableNode())
1446         {
1447             // Handle section break between tables.
1448             const SwTableNode* pTableNode = static_cast<SwTableNode*>(&aNextIndex.GetNode());
1449             const SwFrameFormat *pFormat = pTableNode->GetTable().GetFrameFormat();
1450             m_rExport.OutputSectionBreaks(&(pFormat->GetAttrSet()), *pTableNode);
1451         }
1452     }
1453 }
1454 
StartParagraphProperties()1455 void DocxAttributeOutput::StartParagraphProperties()
1456 {
1457     m_pSerializer->mark(Tag_StartParagraphProperties);
1458 
1459     m_pSerializer->startElementNS(XML_w, XML_pPr);
1460 
1461     // and output the section break now (if it appeared)
1462     if (m_pSectionInfo && m_rExport.m_nTextTyp == TXT_MAINTEXT)
1463     {
1464         m_rExport.SectionProperties( *m_pSectionInfo );
1465         m_pSectionInfo.reset();
1466     }
1467 
1468     InitCollectedParagraphProperties();
1469 }
1470 
InitCollectedParagraphProperties()1471 void DocxAttributeOutput::InitCollectedParagraphProperties()
1472 {
1473     m_pLRSpaceAttrList.clear();
1474     m_pParagraphSpacingAttrList.clear();
1475 
1476     // Write the elements in the spec order
1477     static const sal_Int32 aOrder[] =
1478     {
1479         FSNS( XML_w, XML_pStyle ),
1480         FSNS( XML_w, XML_keepNext ),
1481         FSNS( XML_w, XML_keepLines ),
1482         FSNS( XML_w, XML_pageBreakBefore ),
1483         FSNS( XML_w, XML_framePr ),
1484         FSNS( XML_w, XML_widowControl ),
1485         FSNS( XML_w, XML_numPr ),
1486         FSNS( XML_w, XML_suppressLineNumbers ),
1487         FSNS( XML_w, XML_pBdr ),
1488         FSNS( XML_w, XML_shd ),
1489         FSNS( XML_w, XML_tabs ),
1490         FSNS( XML_w, XML_suppressAutoHyphens ),
1491         FSNS( XML_w, XML_kinsoku ),
1492         FSNS( XML_w, XML_wordWrap ),
1493         FSNS( XML_w, XML_overflowPunct ),
1494         FSNS( XML_w, XML_topLinePunct ),
1495         FSNS( XML_w, XML_autoSpaceDE ),
1496         FSNS( XML_w, XML_autoSpaceDN ),
1497         FSNS( XML_w, XML_bidi ),
1498         FSNS( XML_w, XML_adjustRightInd ),
1499         FSNS( XML_w, XML_snapToGrid ),
1500         FSNS( XML_w, XML_spacing ),
1501         FSNS( XML_w, XML_ind ),
1502         FSNS( XML_w, XML_contextualSpacing ),
1503         FSNS( XML_w, XML_mirrorIndents ),
1504         FSNS( XML_w, XML_suppressOverlap ),
1505         FSNS( XML_w, XML_jc ),
1506         FSNS( XML_w, XML_textDirection ),
1507         FSNS( XML_w, XML_textAlignment ),
1508         FSNS( XML_w, XML_textboxTightWrap ),
1509         FSNS( XML_w, XML_outlineLvl ),
1510         FSNS( XML_w, XML_divId ),
1511         FSNS( XML_w, XML_cnfStyle ),
1512         FSNS( XML_w, XML_rPr ),
1513         FSNS( XML_w, XML_sectPr ),
1514         FSNS( XML_w, XML_pPrChange )
1515     };
1516 
1517     // postpone the output so that we can later [in EndParagraphProperties()]
1518     // prepend the properties before the run
1519     // coverity[overrun-buffer-arg : FALSE] - coverity has difficulty with css::uno::Sequence
1520     m_pSerializer->mark(Tag_InitCollectedParagraphProperties, comphelper::containerToSequence(aOrder));
1521 }
1522 
WriteCollectedParagraphProperties()1523 void DocxAttributeOutput::WriteCollectedParagraphProperties()
1524 {
1525     if ( m_rExport.SdrExporter().getFlyAttrList().is() )
1526     {
1527         m_pSerializer->singleElementNS( XML_w, XML_framePr,
1528                                         detachFrom(m_rExport.SdrExporter().getFlyAttrList() ) );
1529     }
1530 
1531     if (m_pLRSpaceAttrList.is())
1532     {
1533         m_pSerializer->singleElementNS(XML_w, XML_ind, detachFrom(m_pLRSpaceAttrList));
1534     }
1535 
1536     if ( m_pParagraphSpacingAttrList.is() )
1537     {
1538         m_pSerializer->singleElementNS( XML_w, XML_spacing, detachFrom( m_pParagraphSpacingAttrList ) );
1539     }
1540 
1541     if ( m_pBackgroundAttrList.is() )
1542     {
1543         m_pSerializer->singleElementNS( XML_w, XML_shd, detachFrom( m_pBackgroundAttrList ) );
1544         m_aFramePr.SetUseFrameBackground(false);
1545     }
1546 }
1547 
1548 namespace
1549 {
1550 
1551 /// Outputs an item set, that contains the formatting of the paragraph marker.
lcl_writeParagraphMarkerProperties(DocxAttributeOutput & rAttributeOutput,const SfxItemSet & rParagraphMarkerProperties)1552 void lcl_writeParagraphMarkerProperties(DocxAttributeOutput& rAttributeOutput, const SfxItemSet& rParagraphMarkerProperties)
1553 {
1554     const SfxItemSet* pOldI = rAttributeOutput.GetExport().GetCurItemSet();
1555     rAttributeOutput.GetExport().SetCurItemSet(&rParagraphMarkerProperties);
1556 
1557     SfxWhichIter aIter(rParagraphMarkerProperties);
1558     sal_uInt16 nWhichId = aIter.FirstWhich();
1559     const SfxPoolItem* pItem = nullptr;
1560     // Did we already produce a <w:sz> element?
1561     bool bFontSizeWritten = false;
1562     bool bBoldWritten = false;
1563     while (nWhichId)
1564     {
1565         if (aIter.GetItemState(true, &pItem) == SfxItemState::SET)
1566         {
1567             if (isCHRATR(nWhichId) || nWhichId == RES_TXTATR_CHARFMT)
1568             {
1569                 // Will this item produce a <w:sz> element?
1570                 bool bFontSizeItem = nWhichId == RES_CHRATR_FONTSIZE || nWhichId == RES_CHRATR_CJK_FONTSIZE;
1571                 bool bBoldItem = nWhichId == RES_CHRATR_WEIGHT || nWhichId == RES_CHRATR_CJK_WEIGHT;
1572                 if (!(bFontSizeWritten && bFontSizeItem) && !(bBoldWritten && bBoldItem))
1573                     rAttributeOutput.OutputItem(*pItem);
1574                 if (bFontSizeItem)
1575                     bFontSizeWritten = true;
1576                 if (bBoldItem)
1577                     bBoldWritten = true;
1578             }
1579             else if (nWhichId == RES_TXTATR_AUTOFMT)
1580             {
1581                 const SwFormatAutoFormat* pAutoFormat = static_cast<const SwFormatAutoFormat*>(pItem);
1582                 lcl_writeParagraphMarkerProperties(rAttributeOutput, *pAutoFormat->GetStyleHandle());
1583             }
1584         }
1585         nWhichId = aIter.NextWhich();
1586     }
1587     rAttributeOutput.GetExport().SetCurItemSet(pOldI);
1588 }
1589 
1590 const char *RubyAlignValues[] =
1591 {
1592     "center",
1593     "distributeLetter",
1594     "distributeSpace",
1595     "left",
1596     "right",
1597     "rightVertical"
1598 };
1599 
1600 
lclConvertWW8JCToOOXMLRubyAlign(sal_Int32 nJC)1601 const char *lclConvertWW8JCToOOXMLRubyAlign(sal_Int32 nJC)
1602 {
1603     const sal_Int32 nElements = SAL_N_ELEMENTS(RubyAlignValues);
1604     if ( nJC >=0 && nJC < nElements )
1605         return RubyAlignValues[nJC];
1606     return RubyAlignValues[0];
1607 }
1608 
1609 }
1610 
EndParagraphProperties(const SfxItemSet & rParagraphMarkerProperties,const SwRedlineData * pRedlineData,const SwRedlineData * pRedlineParagraphMarkerDeleted,const SwRedlineData * pRedlineParagraphMarkerInserted)1611 void DocxAttributeOutput::EndParagraphProperties(const SfxItemSet& rParagraphMarkerProperties, const SwRedlineData* pRedlineData, const SwRedlineData* pRedlineParagraphMarkerDeleted, const SwRedlineData* pRedlineParagraphMarkerInserted)
1612 {
1613     // Call the 'Redline' function. This will add redline (change-tracking) information that regards to paragraph properties.
1614     // This includes changes like 'Bold', 'Underline', 'Strikethrough' etc.
1615 
1616     // If there is RedlineData present, call WriteCollectedParagraphProperties() for writing pPr before calling Redline().
1617     // As there will be another pPr for redline and LO might mix both.
1618     if(pRedlineData)
1619         WriteCollectedParagraphProperties();
1620     Redline( pRedlineData );
1621 
1622     WriteCollectedParagraphProperties();
1623 
1624     // Merge the marks for the ordered elements
1625     m_pSerializer->mergeTopMarks(Tag_InitCollectedParagraphProperties);
1626 
1627     // Write 'Paragraph Mark' properties
1628     m_pSerializer->startElementNS(XML_w, XML_rPr);
1629     // mark() before paragraph mark properties child elements.
1630     InitCollectedRunProperties();
1631 
1632     // The 'm_pFontsAttrList', 'm_pEastAsianLayoutAttrList', 'm_pCharLangAttrList' are used to hold information
1633     // that should be collected by different properties in the core, and are all flushed together
1634     // to the DOCX when the function 'WriteCollectedRunProperties' gets called.
1635     // So we need to store the current status of these lists, so that we can revert back to them when
1636     // we are done exporting the redline attributes.
1637     auto pFontsAttrList_Original(detachFrom(m_pFontsAttrList));
1638     auto pEastAsianLayoutAttrList_Original(detachFrom(m_pEastAsianLayoutAttrList));
1639     auto pCharLangAttrList_Original(detachFrom(m_pCharLangAttrList));
1640 
1641     lcl_writeParagraphMarkerProperties(*this, rParagraphMarkerProperties);
1642 
1643     // Write the collected run properties that are stored in 'm_pFontsAttrList', 'm_pEastAsianLayoutAttrList', 'm_pCharLangAttrList'
1644     WriteCollectedRunProperties();
1645 
1646     // Revert back the original values that were stored in 'm_pFontsAttrList', 'm_pEastAsianLayoutAttrList', 'm_pCharLangAttrList'
1647     m_pFontsAttrList = std::move(pFontsAttrList_Original);
1648     m_pEastAsianLayoutAttrList = std::move(pEastAsianLayoutAttrList_Original);
1649     m_pCharLangAttrList = std::move(pCharLangAttrList_Original);
1650 
1651     if ( pRedlineParagraphMarkerDeleted )
1652     {
1653         StartRedline( pRedlineParagraphMarkerDeleted, /*bLastRun=*/true );
1654         EndRedline( pRedlineParagraphMarkerDeleted, /*bLastRun=*/true );
1655     }
1656     if ( pRedlineParagraphMarkerInserted )
1657     {
1658         StartRedline( pRedlineParagraphMarkerInserted, /*bLastRun=*/true );
1659         EndRedline( pRedlineParagraphMarkerInserted, /*bLastRun=*/true );
1660     }
1661 
1662     // mergeTopMarks() after paragraph mark properties child elements.
1663     m_pSerializer->mergeTopMarks(Tag_InitCollectedRunProperties);
1664     m_pSerializer->endElementNS( XML_w, XML_rPr );
1665 
1666     if (!m_bWritingHeaderFooter && m_aFramePr.Frame())
1667     {
1668         const SwFrameFormat& rFrameFormat = m_aFramePr.Frame()->GetFrameFormat();
1669         assert(TextBoxIsFramePr(rFrameFormat) && "by definition, because Frame()");
1670 
1671         const Size aSize = m_aFramePr.Frame()->GetSize();
1672         PopulateFrameProperties(&rFrameFormat, aSize);
1673 
1674         // if the paragraph itself never called FormatBox, do so now
1675         if (m_aFramePr.UseFrameBorders(!m_xTableWrt ? -1 : m_tableReference.m_nTableDepth))
1676             FormatBox(rFrameFormat.GetBox());
1677 
1678         if (m_aFramePr.UseFrameBackground())
1679         {
1680             // The frame is usually imported as 100% transparent. Ignore in that case.
1681             // Background only exports as fully opaque. Emulate - ignore transparency more than 50%
1682             const SwAttrSet& rSet = rFrameFormat.GetAttrSet();
1683             const XFillStyleItem* pFillStyle(rSet.GetItem<XFillStyleItem>(XATTR_FILLSTYLE));
1684             if (pFillStyle && pFillStyle->GetValue() != drawing::FillStyle_NONE)
1685             {
1686                 std::unique_ptr<SvxBrushItem> pBrush(
1687                     getSvxBrushItemFromSourceSet(rSet, RES_BACKGROUND));
1688                 if (pBrush->GetColor().GetAlpha() > 127) // more opaque than transparent
1689                 {
1690                     FormatBackground(*pBrush);
1691                     WriteCollectedParagraphProperties();
1692                 }
1693             }
1694         }
1695 
1696         if (m_aFramePr.UseFrameTextDirection(!m_xTableWrt ? -1 : m_tableReference.m_nTableDepth))
1697         {
1698             const SvxFrameDirectionItem& rFrameDir = rFrameFormat.GetFrameDir();
1699             if (rFrameDir.GetValue() != SvxFrameDirection::Environment)
1700             {
1701                 assert(!m_rExport.m_bOutPageDescs);
1702                 // hack: use existing variable to write out the full TextDirection attribute.
1703                 // This is valid for paragraphs/styles - just not native in LO, so hack for now.
1704                 m_rExport.m_bOutPageDescs = true;
1705                 FormatFrameDirection(rFrameDir);
1706                 m_rExport.m_bOutPageDescs = false;
1707             }
1708         }
1709 
1710         // reset to true in preparation for the next paragraph in the frame
1711         m_aFramePr.SetUseFrameBorders(true);
1712         m_aFramePr.SetUseFrameBackground(true);
1713         m_aFramePr.SetUseFrameTextDirection(true);
1714     }
1715 
1716     m_pSerializer->endElementNS( XML_w, XML_pPr );
1717 
1718     // RDF metadata for this text node.
1719     SwTextNode* pTextNode = m_rExport.m_pCurPam->GetPointNode().GetTextNode();
1720     std::map<OUString, OUString> aStatements;
1721     if (pTextNode)
1722         aStatements = SwRDFHelper::getTextNodeStatements(u"urn:bails"_ustr, *pTextNode);
1723     if (!aStatements.empty())
1724     {
1725         m_pSerializer->startElementNS(XML_w, XML_smartTag,
1726                                       FSNS(XML_w, XML_uri), "http://www.w3.org/1999/02/22-rdf-syntax-ns#",
1727                                       FSNS(XML_w, XML_element), "RDF");
1728         m_pSerializer->startElementNS(XML_w, XML_smartTagPr);
1729         for (const auto& rStatement : aStatements)
1730             m_pSerializer->singleElementNS(XML_w, XML_attr,
1731                                            FSNS(XML_w, XML_name), rStatement.first,
1732                                            FSNS(XML_w, XML_val), rStatement.second);
1733         m_pSerializer->endElementNS(XML_w, XML_smartTagPr);
1734         m_pSerializer->endElementNS(XML_w, XML_smartTag);
1735     }
1736 
1737     if ((m_nColBreakStatus == COLBRK_WRITE || m_nColBreakStatus == COLBRK_WRITEANDPOSTPONE)
1738         && !m_rExport.SdrExporter().IsDMLAndVMLDrawingOpen())
1739     {
1740         m_pSerializer->startElementNS(XML_w, XML_r);
1741         m_pSerializer->singleElementNS(XML_w, XML_br, FSNS(XML_w, XML_type), "column");
1742         m_pSerializer->endElementNS( XML_w, XML_r );
1743 
1744         if ( m_nColBreakStatus == COLBRK_WRITEANDPOSTPONE )
1745             m_nColBreakStatus = COLBRK_POSTPONE;
1746         else
1747             m_nColBreakStatus = COLBRK_NONE;
1748     }
1749 
1750     if (m_bPostponedPageBreak && !m_bWritingHeaderFooter
1751         && !m_rExport.SdrExporter().IsDMLAndVMLDrawingOpen())
1752     {
1753         m_pSerializer->startElementNS(XML_w, XML_r);
1754         m_pSerializer->singleElementNS(XML_w, XML_br, FSNS(XML_w, XML_type), "page");
1755         m_pSerializer->endElementNS( XML_w, XML_r );
1756 
1757         m_bPostponedPageBreak = false;
1758     }
1759 
1760     // merge the properties _before_ the run (strictly speaking, just
1761     // after the start of the paragraph)
1762     m_pSerializer->mergeTopMarks(Tag_StartParagraphProperties, sax_fastparser::MergeMarks::PREPEND);
1763 }
1764 
SetStateOfFlyFrame(FlyProcessingState nStateOfFlyFrame)1765 void DocxAttributeOutput::SetStateOfFlyFrame( FlyProcessingState nStateOfFlyFrame )
1766 {
1767     m_nStateOfFlyFrame = nStateOfFlyFrame;
1768 }
1769 
SetAnchorIsLinkedToNode(bool bAnchorLinkedToNode)1770 void DocxAttributeOutput::SetAnchorIsLinkedToNode( bool bAnchorLinkedToNode )
1771 {
1772     m_bAnchorLinkedToNode = bAnchorLinkedToNode ;
1773 }
1774 
ResetFlyProcessingFlag()1775 void DocxAttributeOutput::ResetFlyProcessingFlag()
1776 {
1777     m_bPostponedProcessingFly = false ;
1778 }
1779 
IsFlyProcessingPostponed()1780 bool DocxAttributeOutput::IsFlyProcessingPostponed()
1781 {
1782     return m_bPostponedProcessingFly;
1783 }
1784 
StartRun(const SwRedlineData * pRedlineData,sal_Int32,bool)1785 void DocxAttributeOutput::StartRun( const SwRedlineData* pRedlineData, sal_Int32 /*nPos*/, bool /*bSingleEmptyRun*/ )
1786 {
1787     // Don't start redline data here, possibly there is a hyperlink later, and
1788     // that has to be started first.
1789     m_pRedlineData = pRedlineData;
1790 
1791     // this mark is used to be able to enclose the run inside a sdr tag.
1792     m_pSerializer->mark(Tag_StartRun_1);
1793 
1794     // postpone the output of the start of a run (there are elements that need
1795     // to be written before the start of the run, but we learn which they are
1796     // _inside_ of the run)
1797     m_pSerializer->mark(Tag_StartRun_2); // let's call it "postponed run start"
1798 
1799     // postpone the output of the text (we get it before the run properties,
1800     // but must write it after them)
1801     m_pSerializer->mark(Tag_StartRun_3); // let's call it "postponed text"
1802 }
1803 
EndRun(const SwTextNode * pNode,sal_Int32 nPos,sal_Int32 nLen,bool bLastRun)1804 void DocxAttributeOutput::EndRun(const SwTextNode* pNode, sal_Int32 nPos, sal_Int32 nLen, bool bLastRun)
1805 {
1806     int nFieldsInPrevHyperlink = m_nFieldsInHyperlink;
1807     // Reset m_nFieldsInHyperlink if a new hyperlink is about to start
1808     if ( m_pHyperlinkAttrList.is() )
1809     {
1810         m_nFieldsInHyperlink = 0;
1811     }
1812 
1813     // Write field starts
1814     for ( std::vector<FieldInfos>::iterator pIt = m_Fields.begin() + nFieldsInPrevHyperlink; pIt != m_Fields.end(); )
1815     {
1816         // Add the fields starts for all but hyperlinks and TOCs
1817         if (pIt->bOpen && pIt->pField && pIt->eType != ww::eFORMDROPDOWN &&
1818             // it is not an input field with extra grabbag params (sdt field)
1819             (!(pIt->eType == ww::eFILLIN && static_cast<const SwInputField*>(pIt->pField.get())->getGrabBagParams().hasElements()))
1820             )
1821         {
1822             StartField_Impl( pNode, nPos, *pIt );
1823 
1824             // Remove the field from the stack if only the start has to be written
1825             // Unknown fields should be removed too
1826             if ( !pIt->bClose || ( pIt->eType == ww::eUNKNOWN ) )
1827             {
1828                 pIt = m_Fields.erase( pIt );
1829                 continue;
1830             }
1831 
1832             if (m_nHyperLinkCount.back() > 0 || m_pHyperlinkAttrList.is())
1833             {
1834                 ++m_nFieldsInHyperlink;
1835             }
1836         }
1837         ++pIt;
1838     }
1839 
1840     // write the run properties + the text, already in the correct order
1841     m_pSerializer->mergeTopMarks(Tag_StartRun_3); // merges with "postponed text", see above
1842 
1843     // level down, to be able to prepend the actual run start attribute (just
1844     // before "postponed run start")
1845     m_pSerializer->mark(Tag_EndRun_1); // let's call it "actual run start"
1846     bool bCloseEarlierSDT = false;
1847 
1848     if (m_bEndCharSdt)
1849     {
1850         // This is the common case: "close sdt before the current run" was requested by the next run.
1851 
1852         // if another sdt starts in this run, then wait
1853         // as closing the sdt now, might cause nesting of sdts
1854         if (m_aRunSdt.m_nSdtPrToken > 0)
1855             bCloseEarlierSDT = true;
1856         else
1857             m_aRunSdt.EndSdtBlock(m_pSerializer);
1858         m_bEndCharSdt = false;
1859     }
1860 
1861     if ( m_closeHyperlinkInPreviousRun )
1862     {
1863         if (m_nHyperLinkCount.back() > 0)
1864         {
1865             for ( int i = 0; i < nFieldsInPrevHyperlink; i++ )
1866             {
1867                 // If fields begin before hyperlink then
1868                 // it should end before hyperlink close
1869                 EndField_Impl( pNode, nPos, m_Fields.back( ) );
1870                 m_Fields.pop_back();
1871             }
1872             m_pSerializer->endElementNS( XML_w, XML_hyperlink );
1873             m_endPageRef = false;
1874             m_nHyperLinkCount.back()--;
1875             m_closeHyperlinkInPreviousRun = false;
1876         }
1877         else
1878         {
1879             bool bIsStartedHyperlink = false;
1880             for (const sal_Int32 nLinkCount : m_nHyperLinkCount)
1881             {
1882                 if (nLinkCount > 0)
1883                 {
1884                     bIsStartedHyperlink = true;
1885                     break;
1886                 }
1887             }
1888             if (!bIsStartedHyperlink)
1889                 m_closeHyperlinkInPreviousRun = false;
1890         }
1891     }
1892 
1893     // Write the hyperlink and toc fields starts
1894     for ( std::vector<FieldInfos>::iterator pIt = m_Fields.begin(); pIt != m_Fields.end(); )
1895     {
1896         // Add the fields starts for hyperlinks, TOCs and index marks
1897         if (pIt->bOpen && (!pIt->pField || pIt->eType == ww::eFORMDROPDOWN ||
1898             // InputField with extra grabbag params - it is sdt field
1899             (pIt->eType == ww::eFILLIN && static_cast<const SwInputField*>(pIt->pField.get())->getGrabBagParams().hasElements())))
1900         {
1901             StartRedline( m_pRedlineData, bLastRun );
1902             StartField_Impl( pNode, nPos, *pIt, true );
1903             EndRedline( m_pRedlineData, bLastRun );
1904 
1905             if (m_nHyperLinkCount.back() > 0)
1906                 ++m_nFieldsInHyperlink;
1907 
1908             // Remove the field if no end needs to be written
1909             if (!pIt->bSep)
1910             {
1911                 pIt = m_Fields.erase( pIt );
1912                 continue;
1913             }
1914         }
1915         if (pIt->bSep && !pIt->pField)
1916         {
1917             // for TOXMark:
1918             // Word ignores bookmarks in field result that is empty;
1919             // work around this by writing bookmark into field command.
1920             if (!m_sFieldBkm.isEmpty())
1921             {
1922                 DoWriteBookmarkTagStart(m_sFieldBkm);
1923                 DoWriteBookmarkTagEnd(m_nNextBookmarkId);
1924                 m_nNextBookmarkId++;
1925                 m_sFieldBkm.clear();
1926             }
1927             CmdEndField_Impl(pNode, nPos, true);
1928             // Remove the field if no end needs to be written
1929             if (!pIt->bClose)
1930             {
1931                 pIt = m_Fields.erase( pIt );
1932                 continue;
1933             }
1934         }
1935         ++pIt;
1936     }
1937 
1938     // Start the hyperlink after the fields separators or we would generate invalid file
1939     bool newStartedHyperlink(false);
1940     if ( m_pHyperlinkAttrList.is() )
1941     {
1942         // if we are ending a hyperlink and there's another one starting here,
1943         // don't do this, so that the fields are closed further down when
1944         // the end hyperlink is handled, which is more likely to put the end in
1945         // the right place, as far as i can tell (not very far in this muck)
1946         if (!m_closeHyperlinkInThisRun)
1947         {
1948             // end ToX fields that want to end _before_ starting the hyperlink
1949             for (auto it = m_Fields.rbegin(); it != m_Fields.rend(); )
1950             {
1951                 if (it->bClose && !it->pField)
1952                 {
1953                     EndField_Impl( pNode, nPos, *it );
1954                     it = decltype(m_Fields)::reverse_iterator(m_Fields.erase(it.base() - 1));
1955                 }
1956                 else
1957                 {
1958                     ++it;
1959                 }
1960             }
1961         }
1962         newStartedHyperlink = true;
1963 
1964         m_pSerializer->startElementNS( XML_w, XML_hyperlink, detachFrom( m_pHyperlinkAttrList ) );
1965         m_nHyperLinkCount.back()++;
1966     }
1967 
1968     // if there is some redlining in the document, output it
1969     bool bSkipRedline = false;
1970     if (nLen == 1)
1971     {
1972         // Don't redline content-controls--Word doesn't do them.
1973         SwTextAttr* pAttr
1974             = pNode->GetTextAttrAt(nPos, RES_TXTATR_CONTENTCONTROL, sw::GetTextAttrMode::Default);
1975         if (pAttr && pAttr->GetStart() == nPos)
1976         {
1977             bSkipRedline = true;
1978         }
1979     }
1980 
1981     if (!bSkipRedline)
1982     {
1983         StartRedline(m_pRedlineData, bLastRun);
1984     }
1985 
1986     // XML_r node should be surrounded with bookmark-begin and bookmark-end nodes if it has bookmarks.
1987     // The same is applied for permission ranges.
1988     // But due to unit test "testFdo85542" let's output bookmark-begin with bookmark-end.
1989     DoWriteBookmarksStart(m_rBookmarksStart, m_pMoveRedlineData);
1990     DoWriteBookmarksEnd(m_rBookmarksEnd);
1991     DoWritePermissionsStart();
1992     DoWriteAnnotationMarks();
1993 
1994     if (m_closeHyperlinkInThisRun && m_nHyperLinkCount.back() > 0 && !m_hyperLinkAnchor.isEmpty()
1995         && m_hyperLinkAnchor.startsWith("_Toc"))
1996     {
1997         OUString sToken;
1998         m_pSerializer->startElementNS(XML_w, XML_r);
1999         m_pSerializer->startElementNS(XML_w, XML_rPr);
2000         m_pSerializer->singleElementNS(XML_w, XML_webHidden);
2001         m_pSerializer->endElementNS( XML_w, XML_rPr );
2002         m_pSerializer->startElementNS(XML_w, XML_fldChar, FSNS(XML_w, XML_fldCharType), "begin");
2003         m_pSerializer->endElementNS( XML_w, XML_fldChar );
2004         m_pSerializer->endElementNS( XML_w, XML_r );
2005 
2006 
2007         m_pSerializer->startElementNS(XML_w, XML_r);
2008         m_pSerializer->startElementNS(XML_w, XML_rPr);
2009         m_pSerializer->singleElementNS(XML_w, XML_webHidden);
2010         m_pSerializer->endElementNS( XML_w, XML_rPr );
2011         sToken = "PAGEREF " + m_hyperLinkAnchor + " \\h"; // '\h' Creates a hyperlink to the bookmarked paragraph.
2012         DoWriteCmd( sToken );
2013         m_pSerializer->endElementNS( XML_w, XML_r );
2014 
2015         // Write the Field separator
2016         m_pSerializer->startElementNS(XML_w, XML_r);
2017         m_pSerializer->startElementNS(XML_w, XML_rPr);
2018         m_pSerializer->singleElementNS(XML_w, XML_webHidden);
2019         m_pSerializer->endElementNS( XML_w, XML_rPr );
2020         m_pSerializer->singleElementNS( XML_w, XML_fldChar,
2021                 FSNS( XML_w, XML_fldCharType ), "separate" );
2022         m_pSerializer->endElementNS( XML_w, XML_r );
2023         // At start of every "PAGEREF" field m_endPageRef value should be true.
2024         m_endPageRef = true;
2025     }
2026 
2027     DoWriteBookmarkStartIfExist(nPos);
2028 
2029     if (nLen != -1)
2030     {
2031         SwTextAttr* pAttr = pNode->GetTextAttrAt(nPos, RES_TXTATR_CONTENTCONTROL, ::sw::GetTextAttrMode::Default);
2032         if (pAttr && pAttr->GetStart() == nPos)
2033         {
2034             auto pTextContentControl = static_txtattr_cast<SwTextContentControl*>(pAttr);
2035             m_pContentControl = pTextContentControl->GetContentControl().GetContentControl();
2036             if (!m_tableReference.m_bTableCellChanged)
2037             {
2038                 WriteContentControlStart();
2039             }
2040         }
2041     }
2042 
2043     m_pSerializer->startElementNS(XML_w, XML_r);
2044     if(GetExport().m_bTabInTOC && m_pHyperlinkAttrList.is())
2045     {
2046         RunText(u"\t"_ustr) ;
2047     }
2048     m_pSerializer->mergeTopMarks(Tag_EndRun_1, sax_fastparser::MergeMarks::PREPEND); // merges with "postponed run start", see above
2049 
2050     if ( !m_sRawText.isEmpty() )
2051     {
2052         RunText( m_sRawText );
2053         m_sRawText.clear();
2054     }
2055 
2056     // write the run start + the run content
2057     m_pSerializer->mergeTopMarks(Tag_StartRun_2); // merges the "actual run start"
2058     // append the actual run end
2059     m_pSerializer->endElementNS( XML_w, XML_r );
2060 
2061     // if there is some redlining in the document, output it
2062     // (except in the case of fields with multiple runs)
2063     if (!bSkipRedline)
2064     {
2065         EndRedline(m_pRedlineData, bLastRun);
2066     }
2067 
2068     if (nLen != -1)
2069     {
2070         sal_Int32 nEnd = nPos + nLen;
2071         SwTextAttr* pAttr = pNode->GetTextAttrAt(nPos, RES_TXTATR_CONTENTCONTROL, ::sw::GetTextAttrMode::Default);
2072         if (pAttr && *pAttr->GetEnd() == nEnd && !m_tableReference.m_bTableCellChanged)
2073         {
2074             WriteContentControlEnd();
2075         }
2076     }
2077 
2078     // enclose in a sdt block, if necessary: if one is already started, then don't do it for now
2079     // (so on export sdt blocks are never nested ATM)
2080     if ( !m_bAnchorLinkedToNode && !m_aRunSdt.m_bStartedSdt)
2081     {
2082         m_aRunSdt.WriteSdtBlock(m_pSerializer, m_bRunTextIsOn, m_rExport.SdrExporter().IsParagraphHasDrawing());
2083     }
2084     else
2085     {
2086         //These should be written out to the actual Node and not to the anchor.
2087         //Clear them as they will be repopulated when the node is processed.
2088         m_aRunSdt.m_nSdtPrToken = 0;
2089         m_aRunSdt.DeleteAndResetTheLists();
2090     }
2091 
2092     if (bCloseEarlierSDT)
2093     {
2094         m_pSerializer->mark(Tag_EndRun_2);
2095         m_aRunSdt.EndSdtBlock(m_pSerializer);
2096         m_pSerializer->mergeTopMarks(Tag_EndRun_2, sax_fastparser::MergeMarks::PREPEND);
2097     }
2098 
2099     m_pSerializer->mergeTopMarks(Tag_StartRun_1);
2100 
2101     // XML_r node should be surrounded with permission-begin and permission-end nodes if it has permission.
2102     DoWritePermissionsEnd();
2103 
2104     for (const auto& rpMath : m_aPostponedMaths)
2105         WritePostponedMath(rpMath.pMathObject, rpMath.nMathObjAlignment);
2106     m_aPostponedMaths.clear();
2107 
2108     for (const auto& rpControl : m_aPostponedFormControls)
2109         WritePostponedFormControl(rpControl);
2110     m_aPostponedFormControls.clear();
2111 
2112     WritePostponedActiveXControl(false);
2113 
2114     WritePendingPlaceholder();
2115 
2116     if ( !m_bWritingField )
2117     {
2118         m_pRedlineData = nullptr;
2119     }
2120 
2121     if ( m_closeHyperlinkInThisRun )
2122     {
2123         if (m_nHyperLinkCount.back() > 0)
2124         {
2125             if( m_endPageRef )
2126             {
2127                 // Hyperlink is started and fldchar "end" needs to be written for PAGEREF
2128                 m_pSerializer->startElementNS(XML_w, XML_r);
2129                 m_pSerializer->startElementNS(XML_w, XML_rPr);
2130                 m_pSerializer->singleElementNS(XML_w, XML_webHidden);
2131                 m_pSerializer->endElementNS( XML_w, XML_rPr );
2132                 m_pSerializer->singleElementNS( XML_w, XML_fldChar,
2133                         FSNS( XML_w, XML_fldCharType ), "end" );
2134                 m_pSerializer->endElementNS( XML_w, XML_r );
2135                 m_endPageRef = false;
2136                 m_hyperLinkAnchor.clear();
2137             }
2138             for ( int i = 0; i < m_nFieldsInHyperlink; i++ )
2139             {
2140                 // If fields begin after hyperlink start then
2141                 // it should end before hyperlink close
2142                 EndField_Impl( pNode, nPos, m_Fields.back( ) );
2143                 m_Fields.pop_back();
2144             }
2145             m_nFieldsInHyperlink = 0;
2146 
2147             m_pSerializer->endElementNS( XML_w, XML_hyperlink );
2148             m_nHyperLinkCount.back()--;
2149             m_closeHyperlinkInThisRun = false;
2150         }
2151         else
2152         {
2153             bool bIsStartedHyperlink = false;
2154             for (const sal_Int32 nLinkCount : m_nHyperLinkCount)
2155             {
2156                 if (nLinkCount > 0)
2157                 {
2158                     bIsStartedHyperlink = true;
2159                     break;
2160                 }
2161             }
2162             if (!bIsStartedHyperlink)
2163                 m_closeHyperlinkInThisRun = false;
2164         }
2165     }
2166 
2167     if (!newStartedHyperlink)
2168     {
2169         while ( m_Fields.begin() != m_Fields.end() )
2170         {
2171             EndField_Impl( pNode, nPos, m_Fields.front( ) );
2172             m_Fields.erase( m_Fields.begin( ) );
2173         }
2174         m_nFieldsInHyperlink = 0;
2175     }
2176 
2177     // end ToX fields
2178     for (auto it = m_Fields.rbegin(); it != m_Fields.rend(); )
2179     {
2180         if (it->bClose && !it->pField)
2181         {
2182             EndField_Impl( pNode, nPos, *it );
2183             it = decltype(m_Fields)::reverse_iterator(m_Fields.erase(it.base() - 1));
2184         }
2185         else
2186         {
2187             ++it;
2188         }
2189     }
2190 
2191     if ( m_pRedlineData )
2192     {
2193         EndRedline( m_pRedlineData, bLastRun );
2194         m_pRedlineData = nullptr;
2195     }
2196 
2197     DoWriteBookmarksStart(m_rFinalBookmarksStart);
2198     DoWriteBookmarksEnd(m_rFinalBookmarksEnd);
2199     DoWriteBookmarkEndIfExist(nPos);
2200 }
2201 
DoWriteBookmarkTagStart(const OUString & bookmarkName)2202 void DocxAttributeOutput::DoWriteBookmarkTagStart(const OUString& bookmarkName)
2203 {
2204     m_pSerializer->singleElementNS(XML_w, XML_bookmarkStart,
2205         FSNS(XML_w, XML_id), OString::number(m_nNextBookmarkId),
2206         FSNS(XML_w, XML_name), GetExport().BookmarkToWord(bookmarkName));
2207 }
2208 
DoWriteBookmarkTagEnd(sal_Int32 const nId)2209 void DocxAttributeOutput::DoWriteBookmarkTagEnd(sal_Int32 const nId)
2210 {
2211     m_pSerializer->singleElementNS(XML_w, XML_bookmarkEnd,
2212         FSNS(XML_w, XML_id), OString::number(nId));
2213 }
2214 
DoWriteMoveRangeTagStart(std::u16string_view bookmarkName,bool bFrom,const SwRedlineData * pRedlineData)2215 void DocxAttributeOutput::DoWriteMoveRangeTagStart(std::u16string_view bookmarkName,
2216     bool bFrom, const SwRedlineData* pRedlineData)
2217 {
2218     bool bRemovePersonalInfo = SvtSecurityOptions::IsOptionSet(
2219         SvtSecurityOptions::EOption::DocWarnRemovePersonalInfo ) && !SvtSecurityOptions::IsOptionSet(
2220             SvtSecurityOptions::EOption::DocWarnKeepRedlineInfo);
2221 
2222     const OUString &rAuthor( SW_MOD()->GetRedlineAuthor( pRedlineData->GetAuthor() ) );
2223     const DateTime aDateTime = pRedlineData->GetTimeStamp();
2224     bool bNoDate = bRemovePersonalInfo ||
2225         ( aDateTime.GetYear() == 1970 && aDateTime.GetMonth() == 1 && aDateTime.GetDay() == 1 );
2226 
2227     rtl::Reference<sax_fastparser::FastAttributeList> pAttributeList
2228         = sax_fastparser::FastSerializerHelper::createAttrList();
2229 
2230     pAttributeList->add(FSNS(XML_w, XML_id), OString::number(m_nNextBookmarkId));
2231     pAttributeList->add(FSNS(XML_w, XML_author ), bRemovePersonalInfo
2232                     ? "Author" + OString::number( GetExport().GetInfoID(rAuthor) )
2233                     : OUStringToOString(rAuthor, RTL_TEXTENCODING_UTF8));
2234     if (!bNoDate)
2235         pAttributeList->add(FSNS(XML_w, XML_date ), DateTimeToOString( aDateTime ));
2236     pAttributeList->add(FSNS(XML_w, XML_name), bookmarkName);
2237     m_pSerializer->singleElementNS( XML_w, bFrom ? XML_moveFromRangeStart : XML_moveToRangeStart, pAttributeList );
2238 
2239     // tdf#150166 avoid of unpaired moveRangeEnd at moved ToC
2240     m_rSavedBookmarksIds.insert(m_nNextBookmarkId);
2241 }
2242 
DoWriteMoveRangeTagEnd(sal_Int32 const nId,bool bFrom)2243 void DocxAttributeOutput::DoWriteMoveRangeTagEnd(sal_Int32 const nId, bool bFrom)
2244 {
2245     if ( m_rSavedBookmarksIds.count(nId) )
2246     {
2247         m_pSerializer->singleElementNS(XML_w, bFrom
2248                 ? XML_moveFromRangeEnd
2249                 : XML_moveToRangeEnd,
2250             FSNS(XML_w, XML_id), OString::number(nId));
2251 
2252         m_rSavedBookmarksIds.erase(nId);
2253     }
2254 }
2255 
DoWriteBookmarkStartIfExist(sal_Int32 nRunPos)2256 void DocxAttributeOutput::DoWriteBookmarkStartIfExist(sal_Int32 nRunPos)
2257 {
2258     auto aRange = m_aBookmarksOfParagraphStart.equal_range(nRunPos);
2259     for( auto aIter = aRange.first; aIter != aRange.second; ++aIter)
2260     {
2261         DoWriteBookmarkTagStart(aIter->second);
2262         m_rOpenedBookmarksIds[aIter->second] = m_nNextBookmarkId;
2263         m_sLastOpenedBookmark = GetExport().BookmarkToWord(aIter->second);
2264         m_nNextBookmarkId++;
2265     }
2266 }
2267 
DoWriteBookmarkEndIfExist(sal_Int32 nRunPos)2268 void DocxAttributeOutput::DoWriteBookmarkEndIfExist(sal_Int32 nRunPos)
2269 {
2270     auto aRange = m_aBookmarksOfParagraphEnd.equal_range(nRunPos);
2271     for( auto aIter = aRange.first; aIter != aRange.second; ++aIter)
2272     {
2273         // Get the id of the bookmark
2274         auto pPos = m_rOpenedBookmarksIds.find(aIter->second);
2275         if (pPos != m_rOpenedBookmarksIds.end())
2276         {
2277             // Output the bookmark
2278             DoWriteBookmarkTagEnd(pPos->second);
2279             m_rOpenedBookmarksIds.erase(aIter->second);
2280         }
2281     }
2282 }
2283 
2284 /// Write the start bookmarks
DoWriteBookmarksStart(std::vector<OUString> & rStarts,const SwRedlineData * pRedlineData)2285 void DocxAttributeOutput::DoWriteBookmarksStart(std::vector<OUString>& rStarts, const SwRedlineData* pRedlineData)
2286 {
2287     for (const OUString & bookmarkName : rStarts)
2288     {
2289         // Output the bookmark (including MoveBookmark of the tracked moving)
2290         bool bMove = false;
2291         bool bFrom = false;
2292         OUString sBookmarkName = GetExport().BookmarkToWord(bookmarkName, &bMove, &bFrom);
2293         if ( bMove )
2294         {
2295             // TODO: redline data of MoveBookmark is restored from the first redline of the bookmark
2296             // range. But a later deletion within a tracked moving is still imported as plain
2297             // deletion, so check IsMoved() and skip the export of the tracked moving to avoid
2298             // export with bad author or date
2299             if ( pRedlineData && pRedlineData->IsMoved() )
2300                 DoWriteMoveRangeTagStart(sBookmarkName, bFrom, pRedlineData);
2301         }
2302         else
2303             DoWriteBookmarkTagStart(bookmarkName);
2304 
2305         m_rOpenedBookmarksIds[bookmarkName] = m_nNextBookmarkId;
2306         m_sLastOpenedBookmark = sBookmarkName;
2307         m_nNextBookmarkId++;
2308     }
2309     rStarts.clear();
2310 }
2311 
2312 /// export the end bookmarks
DoWriteBookmarksEnd(std::vector<OUString> & rEnds)2313 void DocxAttributeOutput::DoWriteBookmarksEnd(std::vector<OUString>& rEnds)
2314 {
2315     for (const OUString & bookmarkName : rEnds)
2316     {
2317         // Get the id of the bookmark
2318         auto pPos = m_rOpenedBookmarksIds.find(bookmarkName);
2319 
2320         if (pPos != m_rOpenedBookmarksIds.end())
2321         {
2322             bool bMove = false;
2323             bool bFrom = false;
2324             GetExport().BookmarkToWord(bookmarkName, &bMove, &bFrom);
2325             // Output the bookmark (including MoveBookmark of the tracked moving)
2326             if ( bMove )
2327                 DoWriteMoveRangeTagEnd(pPos->second, bFrom);
2328             else
2329                 DoWriteBookmarkTagEnd(pPos->second);
2330 
2331             m_rOpenedBookmarksIds.erase(bookmarkName);
2332         }
2333     }
2334     rEnds.clear();
2335 }
2336 
2337 // For construction of the special bookmark name template for permissions:
2338 // see, PermInsertPosition::createBookmarkName()
2339 //
2340 // Syntax:
2341 // - "permission-for-user:<permission-id>:<permission-user-name>"
2342 // - "permission-for-group:<permission-id>:<permission-group-name>"
2343 //
DoWritePermissionTagStart(std::u16string_view permission)2344 void DocxAttributeOutput::DoWritePermissionTagStart(std::u16string_view permission)
2345 {
2346     std::u16string_view permissionIdAndName;
2347 
2348     if (o3tl::starts_with(permission, u"permission-for-group:", &permissionIdAndName))
2349     {
2350         const std::size_t separatorIndex = permissionIdAndName.find(u':');
2351         assert(separatorIndex != std::u16string_view::npos);
2352         const OUString permissionId(permissionIdAndName.substr(0, separatorIndex));
2353         const OUString permissionName(permissionIdAndName.substr(separatorIndex + 1));
2354 
2355         m_pSerializer->singleElementNS(XML_w, XML_permStart,
2356             FSNS(XML_w, XML_id), GetExport().BookmarkToWord(permissionId),
2357             FSNS(XML_w, XML_edGrp), GetExport().BookmarkToWord(permissionName));
2358     }
2359     else
2360     {
2361         auto const ok = o3tl::starts_with(
2362             permission, u"permission-for-user:", &permissionIdAndName);
2363         assert(ok); (void)ok;
2364         const std::size_t separatorIndex = permissionIdAndName.find(u':');
2365         assert(separatorIndex != std::u16string_view::npos);
2366         const OUString permissionId(permissionIdAndName.substr(0, separatorIndex));
2367         const OUString permissionName(permissionIdAndName.substr(separatorIndex + 1));
2368 
2369         m_pSerializer->singleElementNS(XML_w, XML_permStart,
2370             FSNS(XML_w, XML_id), GetExport().BookmarkToWord(permissionId),
2371             FSNS(XML_w, XML_ed), GetExport().BookmarkToWord(permissionName));
2372     }
2373 }
2374 
2375 
2376 // For construction of the special bookmark name template for permissions:
2377 // see, PermInsertPosition::createBookmarkName()
2378 //
2379 // Syntax:
2380 // - "permission-for-user:<permission-id>:<permission-user-name>"
2381 // - "permission-for-group:<permission-id>:<permission-group-name>"
2382 //
DoWritePermissionTagEnd(std::u16string_view permission)2383 void DocxAttributeOutput::DoWritePermissionTagEnd(std::u16string_view permission)
2384 {
2385     std::u16string_view permissionIdAndName;
2386 
2387     auto const ok = o3tl::starts_with(permission, u"permission-for-group:", &permissionIdAndName) ||
2388         o3tl::starts_with(permission, u"permission-for-user:", &permissionIdAndName);
2389     assert(ok); (void)ok;
2390 
2391     const std::size_t separatorIndex = permissionIdAndName.find(u':');
2392     assert(separatorIndex != std::u16string_view::npos);
2393     const OUString permissionId(permissionIdAndName.substr(0, separatorIndex));
2394 
2395     m_pSerializer->singleElementNS(XML_w, XML_permEnd,
2396         FSNS(XML_w, XML_id), GetExport().BookmarkToWord(permissionId));
2397 }
2398 
2399 /// Write the start permissions
DoWritePermissionsStart()2400 void DocxAttributeOutput::DoWritePermissionsStart()
2401 {
2402     for (const OUString & permission : m_rPermissionsStart)
2403     {
2404         DoWritePermissionTagStart(permission);
2405     }
2406     m_rPermissionsStart.clear();
2407 }
2408 
2409 /// export the end permissions
DoWritePermissionsEnd()2410 void DocxAttributeOutput::DoWritePermissionsEnd()
2411 {
2412     for (const OUString & permission : m_rPermissionsEnd)
2413     {
2414         DoWritePermissionTagEnd(permission);
2415     }
2416     m_rPermissionsEnd.clear();
2417 }
2418 
DoWriteAnnotationMarks()2419 void DocxAttributeOutput::DoWriteAnnotationMarks()
2420 {
2421     // Write the start annotation marks
2422     for ( const auto & rName : m_rAnnotationMarksStart )
2423     {
2424         // Output the annotation mark
2425         /* Ensure that the existing Annotation Marks are not overwritten
2426            as it causes discrepancy when DocxAttributeOutput::PostitField
2427            refers to this map & while mapping comment id's in document.xml &
2428            comment.xml.
2429         */
2430         if ( m_rOpenedAnnotationMarksIds.end() == m_rOpenedAnnotationMarksIds.find( rName ) )
2431         {
2432             const sal_Int32 nId = m_nNextAnnotationMarkId++;
2433             m_rOpenedAnnotationMarksIds[rName] = nId;
2434             m_pSerializer->singleElementNS( XML_w, XML_commentRangeStart,
2435                 FSNS( XML_w, XML_id ), OString::number(nId) );
2436             m_sLastOpenedAnnotationMark = rName;
2437         }
2438     }
2439     m_rAnnotationMarksStart.clear();
2440 
2441     // export the end annotation marks
2442     for ( const auto & rName : m_rAnnotationMarksEnd )
2443     {
2444         // Get the id of the annotation mark
2445         auto pPos = m_rOpenedAnnotationMarksIds.find( rName );
2446         if ( pPos != m_rOpenedAnnotationMarksIds.end(  ) )
2447         {
2448             const sal_Int32 nId = ( *pPos ).second;
2449             m_pSerializer->singleElementNS( XML_w, XML_commentRangeEnd,
2450                 FSNS( XML_w, XML_id ), OString::number(nId) );
2451             m_rOpenedAnnotationMarksIds.erase( rName );
2452 
2453             m_pSerializer->startElementNS(XML_w, XML_r);
2454             m_pSerializer->singleElementNS( XML_w, XML_commentReference, FSNS( XML_w, XML_id ),
2455                                             OString::number(nId) );
2456             m_pSerializer->endElementNS(XML_w, XML_r);
2457         }
2458     }
2459     m_rAnnotationMarksEnd.clear();
2460 }
2461 
WriteFFData(const FieldInfos & rInfos)2462 void DocxAttributeOutput::WriteFFData(  const FieldInfos& rInfos )
2463 {
2464     const ::sw::mark::IFieldmark& rFieldmark = *rInfos.pFieldmark;
2465     FieldMarkParamsHelper params( rFieldmark );
2466 
2467     OUString sEntryMacro;
2468     params.extractParam(u"EntryMacro"_ustr, sEntryMacro);
2469     OUString sExitMacro;
2470     params.extractParam(u"ExitMacro"_ustr, sExitMacro);
2471     OUString sHelp;
2472     params.extractParam(u"Help"_ustr, sHelp);
2473     OUString sHint;
2474     params.extractParam(u"Hint"_ustr, sHint); // .docx StatusText
2475     if ( sHint.isEmpty() )
2476         params.extractParam(u"Description"_ustr, sHint); // .doc StatusText
2477 
2478     if ( rInfos.eType == ww::eFORMDROPDOWN )
2479     {
2480         uno::Sequence< OUString> vListEntries;
2481         OUString sName, sSelected;
2482 
2483         params.extractParam( ODF_FORMDROPDOWN_LISTENTRY, vListEntries );
2484         if (vListEntries.getLength() > ODF_FORMDROPDOWN_ENTRY_COUNT_LIMIT)
2485             vListEntries = uno::Sequence< OUString>(vListEntries.getArray(), ODF_FORMDROPDOWN_ENTRY_COUNT_LIMIT);
2486 
2487         sName = params.getName();
2488         sal_Int32 nSelectedIndex = 0;
2489 
2490         if ( params.extractParam( ODF_FORMDROPDOWN_RESULT, nSelectedIndex ) )
2491         {
2492             if (nSelectedIndex < vListEntries.getLength() )
2493                 sSelected = vListEntries[ nSelectedIndex ];
2494         }
2495 
2496         GetExport().DoComboBox( sName, OUString(), OUString(), sSelected, vListEntries );
2497     }
2498     else if ( rInfos.eType == ww::eFORMCHECKBOX )
2499     {
2500         const OUString sName = params.getName();
2501         bool bChecked = false;
2502 
2503         const sw::mark::ICheckboxFieldmark* pCheckboxFm = dynamic_cast<const sw::mark::ICheckboxFieldmark*>(&rFieldmark);
2504         if ( pCheckboxFm && pCheckboxFm->IsChecked() )
2505             bChecked = true;
2506 
2507         FFDataWriterHelper ffdataOut( m_pSerializer );
2508         ffdataOut.WriteFormCheckbox( sName, sEntryMacro, sExitMacro, sHelp, sHint, bChecked );
2509     }
2510     else if ( rInfos.eType == ww::eFORMTEXT )
2511     {
2512         OUString sType;
2513         params.extractParam(u"Type"_ustr, sType);
2514         OUString sDefaultText;
2515         params.extractParam(u"Content"_ustr, sDefaultText);
2516         sal_uInt16 nMaxLength = 0;
2517         params.extractParam(u"MaxLength"_ustr, nMaxLength);
2518         OUString sFormat;
2519         params.extractParam(u"Format"_ustr, sFormat);
2520         FFDataWriterHelper ffdataOut( m_pSerializer );
2521         ffdataOut.WriteFormText( params.getName(), sEntryMacro, sExitMacro, sHelp, sHint,
2522                                  sType, sDefaultText, nMaxLength, sFormat );
2523     }
2524 }
2525 
WriteFormDateStart(const OUString & sFullDate,const OUString & sDateFormat,const OUString & sLang,const uno::Sequence<beans::PropertyValue> & aGrabBagSdt)2526 void DocxAttributeOutput::WriteFormDateStart(const OUString& sFullDate, const OUString& sDateFormat, const OUString& sLang, const uno::Sequence<beans::PropertyValue>& aGrabBagSdt)
2527 {
2528     m_pSerializer->startElementNS(XML_w, XML_sdt);
2529     m_pSerializer->startElementNS(XML_w, XML_sdtPr);
2530 
2531     if(!sFullDate.isEmpty())
2532         m_pSerializer->startElementNS(XML_w, XML_date, FSNS(XML_w, XML_fullDate), sFullDate);
2533     else
2534         m_pSerializer->startElementNS(XML_w, XML_date);
2535 
2536     // Replace quotation mark used for marking static strings in date format
2537     OUString sDateFormat1 = sDateFormat.replaceAll("\"", "'");
2538     m_pSerializer->singleElementNS(XML_w, XML_dateFormat,
2539                                    FSNS(XML_w, XML_val), sDateFormat1);
2540     m_pSerializer->singleElementNS(XML_w, XML_lid,
2541                                    FSNS(XML_w, XML_val), sLang);
2542     m_pSerializer->singleElementNS(XML_w, XML_storeMappedDataAs,
2543                                    FSNS(XML_w, XML_val), "dateTime");
2544     m_pSerializer->singleElementNS(XML_w, XML_calendar,
2545                                    FSNS(XML_w, XML_val), "gregorian");
2546     m_pSerializer->endElementNS(XML_w, XML_date);
2547 
2548     if (aGrabBagSdt.hasElements())
2549     {
2550         // There are some extra sdt parameters came from grab bag
2551         SdtBlockHelper aSdtBlock;
2552         aSdtBlock.GetSdtParamsFromGrabBag(aGrabBagSdt);
2553         aSdtBlock.WriteExtraParams(m_pSerializer);
2554     }
2555 
2556     m_pSerializer->endElementNS(XML_w, XML_sdtPr);
2557 
2558     m_pSerializer->startElementNS(XML_w, XML_sdtContent);
2559 }
2560 
WriteSdtPlainText(const OUString & sValue,const uno::Sequence<beans::PropertyValue> & aGrabBagSdt)2561 void DocxAttributeOutput::WriteSdtPlainText(const OUString & sValue, const uno::Sequence<beans::PropertyValue>& aGrabBagSdt)
2562 {
2563     m_pSerializer->startElementNS(XML_w, XML_sdt);
2564     m_pSerializer->startElementNS(XML_w, XML_sdtPr);
2565 
2566     if (aGrabBagSdt.hasElements())
2567     {
2568         // There are some extra sdt parameters came from grab bag
2569         SdtBlockHelper aSdtBlock;
2570         aSdtBlock.GetSdtParamsFromGrabBag(aGrabBagSdt);
2571         aSdtBlock.WriteExtraParams(m_pSerializer);
2572 
2573         if (aSdtBlock.m_nSdtPrToken && aSdtBlock.m_nSdtPrToken != FSNS(XML_w, XML_id))
2574         {
2575             // Write <w:text/> or whatsoever from grabbag
2576             m_pSerializer->singleElement(aSdtBlock.m_nSdtPrToken);
2577         }
2578 
2579         // Store databindings data for later writing to corresponding XMLs
2580         OUString sPrefixMapping, sXpath;
2581         for (const auto& rProp : aGrabBagSdt)
2582         {
2583             if (rProp.Name == "ooxml:CT_SdtPr_dataBinding")
2584             {
2585                 uno::Sequence<beans::PropertyValue> aDataBindingProps;
2586                 rProp.Value >>= aDataBindingProps;
2587                 for (const auto& rDBProp : aDataBindingProps)
2588                 {
2589                     if (rDBProp.Name == "ooxml:CT_DataBinding_prefixMappings")
2590                         sPrefixMapping = rDBProp.Value.get<OUString>();
2591                     else if (rDBProp.Name == "ooxml:CT_DataBinding_xpath")
2592                         sXpath = rDBProp.Value.get<OUString>();
2593                 }
2594             }
2595         }
2596 
2597         if (sXpath.getLength())
2598         {
2599             // Given xpath is sufficient
2600             m_rExport.AddSdtData(sPrefixMapping, sXpath, sValue);
2601         }
2602     }
2603 
2604     m_pSerializer->endElementNS(XML_w, XML_sdtPr);
2605 
2606     m_pSerializer->startElementNS(XML_w, XML_sdtContent);
2607 }
2608 
WriteContentControlStart()2609 void DocxAttributeOutput::WriteContentControlStart()
2610 {
2611     if (!m_pContentControl)
2612     {
2613         return;
2614     }
2615 
2616     m_pSerializer->startElementNS(XML_w, XML_sdt);
2617     m_pSerializer->startElementNS(XML_w, XML_sdtPr);
2618     if (!m_pContentControl->GetPlaceholderDocPart().isEmpty())
2619     {
2620         m_pSerializer->startElementNS(XML_w, XML_placeholder);
2621         m_pSerializer->singleElementNS(XML_w, XML_docPart, FSNS(XML_w, XML_val),
2622                                        m_pContentControl->GetPlaceholderDocPart());
2623         m_pSerializer->endElementNS(XML_w, XML_placeholder);
2624     }
2625 
2626     if (!m_pContentControl->GetDataBindingPrefixMappings().isEmpty() || !m_pContentControl->GetDataBindingXpath().isEmpty() || !m_pContentControl->GetDataBindingStoreItemID().isEmpty())
2627     {
2628         m_pSerializer->singleElementNS( XML_w, XML_dataBinding,
2629             FSNS(XML_w, XML_prefixMappings), m_pContentControl->GetDataBindingPrefixMappings(),
2630             FSNS(XML_w, XML_xpath), m_pContentControl->GetDataBindingXpath(),
2631             FSNS(XML_w, XML_storeItemID), m_pContentControl->GetDataBindingStoreItemID());
2632     }
2633 
2634     if (!m_pContentControl->GetColor().isEmpty())
2635     {
2636         m_pSerializer->singleElementNS(XML_w15, XML_color, FSNS(XML_w, XML_val),
2637                                        m_pContentControl->GetColor());
2638     }
2639 
2640     if (!m_pContentControl->GetAppearance().isEmpty())
2641     {
2642         m_pSerializer->singleElementNS(XML_w15, XML_appearance, FSNS(XML_w15, XML_val),
2643                                        m_pContentControl->GetAppearance());
2644     }
2645 
2646     if (!m_pContentControl->GetAlias().isEmpty())
2647     {
2648         m_pSerializer->singleElementNS(XML_w, XML_alias, FSNS(XML_w, XML_val),
2649                                        m_pContentControl->GetAlias());
2650     }
2651 
2652     if (!m_pContentControl->GetTag().isEmpty())
2653     {
2654         m_pSerializer->singleElementNS(XML_w, XML_tag, FSNS(XML_w, XML_val),
2655                                        m_pContentControl->GetTag());
2656     }
2657 
2658     if (m_pContentControl->GetId())
2659     {
2660         m_pSerializer->singleElementNS(XML_w, XML_id, FSNS(XML_w, XML_val),
2661                                        OString::number(m_pContentControl->GetId()));
2662     }
2663 
2664     if (m_pContentControl->GetTabIndex())
2665     {
2666         // write the unsigned value as if it were signed since that is all we can import
2667         const sal_Int32 nTabIndex = static_cast<sal_Int32>(m_pContentControl->GetTabIndex());
2668         m_pSerializer->singleElementNS(XML_w, XML_tabIndex, FSNS(XML_w, XML_val),
2669                                        OString::number(nTabIndex));
2670     }
2671 
2672     if (!m_pContentControl->GetLock().isEmpty())
2673     {
2674         m_pSerializer->singleElementNS(XML_w, XML_lock, FSNS(XML_w, XML_val),
2675                                        m_pContentControl->GetLock());
2676     }
2677 
2678     if (m_pContentControl->GetShowingPlaceHolder())
2679     {
2680         m_pSerializer->singleElementNS(XML_w, XML_showingPlcHdr);
2681     }
2682 
2683     if (m_pContentControl->GetPicture())
2684     {
2685         m_pSerializer->singleElementNS(XML_w, XML_picture);
2686     }
2687 
2688     if (m_pContentControl->GetCheckbox())
2689     {
2690         m_pSerializer->startElementNS(XML_w14, XML_checkbox);
2691         m_pSerializer->singleElementNS(XML_w14, XML_checked, FSNS(XML_w14, XML_val),
2692                                        OString::number(int(m_pContentControl->GetChecked())));
2693         OUString aCheckedState = m_pContentControl->GetCheckedState();
2694         if (!aCheckedState.isEmpty())
2695         {
2696             m_pSerializer->singleElementNS(XML_w14, XML_checkedState, FSNS(XML_w14, XML_val),
2697                                            OString::number(aCheckedState[0], /*radix=*/16));
2698         }
2699         OUString aUncheckedState = m_pContentControl->GetUncheckedState();
2700         if (!aUncheckedState.isEmpty())
2701         {
2702             m_pSerializer->singleElementNS(XML_w14, XML_uncheckedState, FSNS(XML_w14, XML_val),
2703                                            OString::number(aUncheckedState[0], /*radix=*/16));
2704         }
2705         m_pSerializer->endElementNS(XML_w14, XML_checkbox);
2706     }
2707 
2708     if (m_pContentControl->GetComboBox() || m_pContentControl->GetDropDown())
2709     {
2710         if (m_pContentControl->GetComboBox())
2711         {
2712             m_pSerializer->startElementNS(XML_w, XML_comboBox);
2713         }
2714         else
2715         {
2716             m_pSerializer->startElementNS(XML_w, XML_dropDownList);
2717         }
2718         for (const auto& rItem : m_pContentControl->GetListItems())
2719         {
2720             rtl::Reference<FastAttributeList> xAttributes = FastSerializerHelper::createAttrList();
2721             if (!rItem.m_aDisplayText.isEmpty())
2722             {
2723                 // If there is no display text, need to omit the attribute, not write an empty one.
2724                 xAttributes->add(FSNS(XML_w, XML_displayText), rItem.m_aDisplayText);
2725             }
2726             xAttributes->add(FSNS(XML_w, XML_value), rItem.m_aValue);
2727             m_pSerializer->singleElementNS(XML_w, XML_listItem, xAttributes);
2728         }
2729         if (m_pContentControl->GetComboBox())
2730         {
2731             m_pSerializer->endElementNS(XML_w, XML_comboBox);
2732         }
2733         else
2734         {
2735             m_pSerializer->endElementNS(XML_w, XML_dropDownList);
2736         }
2737     }
2738 
2739     if (m_pContentControl->GetDate())
2740     {
2741         OUString aCurrentDate = m_pContentControl->GetCurrentDate();
2742         if (aCurrentDate.isEmpty())
2743         {
2744             m_pSerializer->startElementNS(XML_w, XML_date);
2745         }
2746         else
2747         {
2748             m_pSerializer->startElementNS(XML_w, XML_date, FSNS(XML_w, XML_fullDate), aCurrentDate);
2749         }
2750         OUString aDateFormat = m_pContentControl->GetDateFormat().replaceAll("\"", "'");
2751         if (!aDateFormat.isEmpty())
2752         {
2753             m_pSerializer->singleElementNS(XML_w, XML_dateFormat, FSNS(XML_w, XML_val),
2754                                            aDateFormat);
2755         }
2756         OUString aDateLanguage = m_pContentControl->GetDateLanguage();
2757         if (!aDateLanguage.isEmpty())
2758         {
2759             m_pSerializer->singleElementNS(XML_w, XML_lid, FSNS(XML_w, XML_val),
2760                                            aDateLanguage);
2761         }
2762         m_pSerializer->endElementNS(XML_w, XML_date);
2763     }
2764 
2765     if (!m_pContentControl->GetMultiLine().isEmpty())
2766     {
2767         m_pSerializer->singleElementNS(XML_w, XML_text, FSNS(XML_w, XML_multiLine), m_pContentControl->GetMultiLine());
2768     }
2769     else if (m_pContentControl->GetPlainText())
2770     {
2771         m_pSerializer->singleElementNS(XML_w, XML_text);
2772     }
2773 
2774     m_pSerializer->endElementNS(XML_w, XML_sdtPr);
2775     m_pSerializer->startElementNS(XML_w, XML_sdtContent);
2776 
2777     const OUString& rPrefixMapping = m_pContentControl->GetDataBindingPrefixMappings();
2778     const OUString& rXpath = m_pContentControl->GetDataBindingXpath();
2779     if (!rXpath.isEmpty())
2780     {
2781         // This content control has a data binding, update the data source.
2782         SwTextContentControl* pTextAttr = m_pContentControl->GetTextAttr();
2783         SwTextNode* pTextNode = m_pContentControl->GetTextNode();
2784         if (pTextNode && pTextAttr)
2785         {
2786             SwPosition aPoint(*pTextNode, pTextAttr->GetStart());
2787             SwPosition aMark(*pTextNode, *pTextAttr->GetEnd());
2788             SwPaM aPam(aMark, aPoint);
2789             OUString aSnippet = aPam.GetText();
2790             static sal_Unicode const aForbidden[] = {
2791                 CH_TXTATR_BREAKWORD,
2792                 0
2793             };
2794             aSnippet = comphelper::string::removeAny(aSnippet, aForbidden);
2795             m_rExport.AddSdtData(rPrefixMapping, rXpath, aSnippet);
2796         }
2797     }
2798 
2799     m_pContentControl = nullptr;
2800 }
2801 
WriteContentControlEnd()2802 void DocxAttributeOutput::WriteContentControlEnd()
2803 {
2804     m_pSerializer->endElementNS(XML_w, XML_sdtContent);
2805     m_pSerializer->endElementNS(XML_w, XML_sdt);
2806 }
2807 
WriteSdtDropDownStart(OUString const & rName,OUString const & rSelected,uno::Sequence<OUString> const & rListItems)2808 void DocxAttributeOutput::WriteSdtDropDownStart(
2809         OUString const& rName,
2810         OUString const& rSelected,
2811         uno::Sequence<OUString> const& rListItems)
2812 {
2813     m_pSerializer->startElementNS(XML_w, XML_sdt);
2814     m_pSerializer->startElementNS(XML_w, XML_sdtPr);
2815 
2816     m_pSerializer->singleElementNS(XML_w, XML_alias,
2817         FSNS(XML_w, XML_val), rName);
2818 
2819     sal_Int32 nId = comphelper::findValue(rListItems, rSelected);
2820     if (nId == -1)
2821     {
2822         nId = 0;
2823     }
2824 
2825     m_pSerializer->startElementNS(XML_w, XML_dropDownList,
2826             FSNS(XML_w, XML_lastValue), OString::number(nId));
2827 
2828     for (auto const& rItem : rListItems)
2829     {
2830         auto const item(OUStringToOString(rItem, RTL_TEXTENCODING_UTF8));
2831         m_pSerializer->singleElementNS(XML_w, XML_listItem,
2832                 FSNS(XML_w, XML_value), item,
2833                 FSNS(XML_w, XML_displayText), item);
2834     }
2835 
2836     m_pSerializer->endElementNS(XML_w, XML_dropDownList);
2837     m_pSerializer->endElementNS(XML_w, XML_sdtPr);
2838 
2839     m_pSerializer->startElementNS(XML_w, XML_sdtContent);
2840 }
2841 
WriteSdtDropDownEnd(OUString const & rSelected,uno::Sequence<OUString> const & rListItems)2842 void DocxAttributeOutput::WriteSdtDropDownEnd(OUString const& rSelected,
2843         uno::Sequence<OUString> const& rListItems)
2844 {
2845     // note: rSelected might be empty?
2846     sal_Int32 nId = comphelper::findValue(rListItems, rSelected);
2847     if (nId == -1)
2848     {
2849         nId = 0;
2850     }
2851 
2852     // the lastValue only identifies the entry in the list, also export
2853     // currently selected item's displayText as run content (if one exists)
2854     if (rListItems.size())
2855     {
2856         m_pSerializer->startElementNS(XML_w, XML_r);
2857         m_pSerializer->startElementNS(XML_w, XML_t);
2858         m_pSerializer->writeEscaped(rListItems[nId]);
2859         m_pSerializer->endElementNS(XML_w, XML_t);
2860         m_pSerializer->endElementNS(XML_w, XML_r);
2861     }
2862 
2863     WriteContentControlEnd();
2864 }
2865 
StartField_Impl(const SwTextNode * pNode,sal_Int32 nPos,FieldInfos const & rInfos,bool bWriteRun)2866 void DocxAttributeOutput::StartField_Impl( const SwTextNode* pNode, sal_Int32 nPos, FieldInfos const & rInfos, bool bWriteRun )
2867 {
2868     if ( rInfos.pField && rInfos.eType == ww::eUNKNOWN )
2869     {
2870         // Expand unsupported fields
2871         RunText(rInfos.pField->ExpandField(/*bCached=*/true, nullptr));
2872         return;
2873     }
2874     else if ( rInfos.eType == ww::eFORMDATE )
2875     {
2876         const sw::mark::IDateFieldmark& rFieldmark = dynamic_cast<const sw::mark::IDateFieldmark&>(*rInfos.pFieldmark);
2877         FieldMarkParamsHelper params(rFieldmark);
2878 
2879         OUString sFullDate;
2880         OUString sCurrentDate;
2881         params.extractParam( ODF_FORMDATE_CURRENTDATE, sCurrentDate );
2882         if(!sCurrentDate.isEmpty())
2883         {
2884             sFullDate = sCurrentDate + "T00:00:00Z";
2885         }
2886         else
2887         {
2888             std::pair<bool, double> aResult = rFieldmark.GetCurrentDate();
2889             if(aResult.first)
2890             {
2891                 sFullDate = rFieldmark.GetDateInStandardDateFormat(aResult.second) + "T00:00:00Z";
2892             }
2893         }
2894 
2895         OUString sDateFormat;
2896         params.extractParam( ODF_FORMDATE_DATEFORMAT, sDateFormat );
2897         OUString sLang;
2898         params.extractParam( ODF_FORMDATE_DATEFORMAT_LANGUAGE, sLang );
2899 
2900         uno::Sequence<beans::PropertyValue> aSdtParams;
2901         params.extractParam(UNO_NAME_MISC_OBJ_INTEROPGRABBAG, aSdtParams);
2902 
2903         WriteFormDateStart( sFullDate, sDateFormat, sLang, aSdtParams);
2904         return;
2905     }
2906     else if (rInfos.eType == ww::eFORMDROPDOWN && rInfos.pField)
2907     {
2908         assert(!rInfos.pFieldmark);
2909         SwDropDownField const& rField2(*static_cast<SwDropDownField const*>(rInfos.pField.get()));
2910         WriteSdtDropDownStart(rField2.GetName(),
2911                 rField2.GetSelectedItem(),
2912                 rField2.GetItemSequence());
2913         return;
2914     }
2915     else if (rInfos.eType == ww::eFILLIN)
2916     {
2917         const SwInputField* pField = static_cast<SwInputField const*>(rInfos.pField.get());
2918         if (pField && pField->getGrabBagParams().hasElements())
2919         {
2920             WriteSdtPlainText(pField->GetPar1(), pField->getGrabBagParams());
2921             m_sRawText = pField->GetPar1();  // Write field content also as a fallback
2922             return;
2923         }
2924     }
2925 
2926     if ( rInfos.eType != ww::eNONE ) // HYPERLINK fields are just commands
2927     {
2928         if ( bWriteRun )
2929             m_pSerializer->startElementNS(XML_w, XML_r);
2930 
2931         if ( rInfos.eType == ww::eFORMDROPDOWN )
2932         {
2933             m_pSerializer->startElementNS( XML_w, XML_fldChar,
2934                 FSNS( XML_w, XML_fldCharType ), "begin" );
2935             assert( rInfos.pFieldmark && !rInfos.pField );
2936             WriteFFData(rInfos);
2937             m_pSerializer->endElementNS( XML_w, XML_fldChar );
2938 
2939             if ( bWriteRun )
2940                 m_pSerializer->endElementNS( XML_w, XML_r );
2941 
2942             CmdField_Impl( pNode, nPos, rInfos, bWriteRun );
2943         }
2944         else
2945         {
2946             // Write the field start
2947             if ( rInfos.pField && (rInfos.pField->Which() == SwFieldIds::DateTime) && rInfos.pField->GetSubType() & FIXEDFLD )
2948             {
2949                 m_pSerializer->startElementNS( XML_w, XML_fldChar,
2950                     FSNS( XML_w, XML_fldCharType ), "begin",
2951                     FSNS( XML_w, XML_fldLock ), "true" );
2952             }
2953             else
2954             {
2955                 m_pSerializer->startElementNS( XML_w, XML_fldChar,
2956                     FSNS( XML_w, XML_fldCharType ), "begin" );
2957             }
2958 
2959             if ( rInfos.pFieldmark )
2960                 WriteFFData(  rInfos );
2961 
2962             m_pSerializer->endElementNS( XML_w, XML_fldChar );
2963 
2964             if ( bWriteRun )
2965                 m_pSerializer->endElementNS( XML_w, XML_r );
2966 
2967             // The hyperlinks fields can't be expanded: the value is
2968             // normally in the text run
2969             if ( !rInfos.pField )
2970                 CmdField_Impl( pNode, nPos, rInfos, bWriteRun );
2971             else
2972                 m_bWritingField = true;
2973         }
2974     }
2975 }
2976 
DoWriteCmd(std::u16string_view rCmd)2977 void DocxAttributeOutput::DoWriteCmd( std::u16string_view rCmd )
2978 {
2979     std::u16string_view sCmd = o3tl::trim(rCmd);
2980     if (o3tl::starts_with(sCmd, u"SEQ"))
2981     {
2982         OUString sSeqName( o3tl::trim(msfilter::util::findQuotedText(sCmd, u"SEQ ", '\\')) );
2983         m_aSeqBookmarksNames[sSeqName].push_back(m_sLastOpenedBookmark);
2984     }
2985     // Write the Field command
2986     sal_Int32 nTextToken = XML_instrText;
2987     if ( m_pRedlineData && m_pRedlineData->GetType() == RedlineType::Delete )
2988         nTextToken = XML_delInstrText;
2989 
2990     m_pSerializer->startElementNS(XML_w, nTextToken, FSNS(XML_xml, XML_space), "preserve");
2991     m_pSerializer->writeEscaped( rCmd );
2992     m_pSerializer->endElementNS( XML_w, nTextToken );
2993 
2994 }
2995 
CmdField_Impl(const SwTextNode * pNode,sal_Int32 nPos,FieldInfos const & rInfos,bool bWriteRun)2996 void DocxAttributeOutput::CmdField_Impl( const SwTextNode* pNode, sal_Int32 nPos, FieldInfos const & rInfos, bool bWriteRun )
2997 {
2998     // Write the Field instruction
2999     if ( bWriteRun )
3000     {
3001         bool bWriteCombChars(false);
3002         m_pSerializer->startElementNS(XML_w, XML_r);
3003 
3004         if (rInfos.eType == ww::eEQ)
3005             bWriteCombChars = true;
3006 
3007         DoWriteFieldRunProperties( pNode, nPos, bWriteCombChars );
3008     }
3009 
3010     sal_Int32 nIdx { rInfos.sCmd.isEmpty() ? -1 : 0 };
3011     while ( nIdx >= 0 )
3012     {
3013         OUString sToken = rInfos.sCmd.getToken( 0, '\t', nIdx );
3014         if ( rInfos.eType ==  ww::eCREATEDATE
3015           || rInfos.eType ==  ww::eSAVEDATE
3016           || rInfos.eType ==  ww::ePRINTDATE
3017           || rInfos.eType ==  ww::eDATE
3018           || rInfos.eType ==  ww::eTIME )
3019         {
3020            sToken = sToken.replaceAll("NNNN", "dddd");
3021            sToken = sToken.replaceAll("NN", "ddd");
3022         }
3023         else if ( rInfos.eType == ww::eEquals )
3024         {
3025             // Use original OOXML formula, if it exists and its conversion hasn't been changed
3026             bool bIsChanged = true;
3027             if ( pNode->GetTableBox() )
3028             {
3029                 if ( const SfxGrabBagItem* pItem = pNode->GetTableBox()->GetFrameFormat()->GetAttrSet().GetItem<SfxGrabBagItem>(RES_FRMATR_GRABBAG) )
3030                 {
3031                     OUString sActualFormula = sToken.trim();
3032                     const std::map<OUString, uno::Any>& rGrabBag = pItem->GetGrabBag();
3033                     std::map<OUString, uno::Any>::const_iterator aStoredFormula = rGrabBag.find(u"CellFormulaConverted"_ustr);
3034                     if ( aStoredFormula != rGrabBag.end() && sActualFormula.indexOf('=') == 0 &&
3035                                     o3tl::trim(sActualFormula.subView(1)) == o3tl::trim(aStoredFormula->second.get<OUString>()) )
3036                     {
3037                         aStoredFormula = rGrabBag.find(u"CellFormula"_ustr);
3038                         if ( aStoredFormula != rGrabBag.end() )
3039                         {
3040                             sToken = " =" + aStoredFormula->second.get<OUString>();
3041                             bIsChanged = false;
3042                         }
3043                     }
3044                 }
3045             }
3046 
3047             if ( bIsChanged )
3048             {
3049                 UErrorCode nErr(U_ZERO_ERROR);
3050                 icu::UnicodeString sInput(sToken.getStr());
3051                 // remove < and > around cell references, e.g. <A1> to A1, <A1:B2> to A1:B2
3052                 icu::RegexMatcher aMatcher("<([A-Z]{1,3}[0-9]+(:[A-Z]{1,3}[0-9]+)?)>", sInput, 0, nErr);
3053                 sInput = aMatcher.replaceAll(icu::UnicodeString("$1"), nErr);
3054                 // convert MEAN to AVERAGE
3055                 icu::RegexMatcher aMatcher2("\\bMEAN\\b", sInput, UREGEX_CASE_INSENSITIVE, nErr);
3056                 sToken = aMatcher2.replaceAll(icu::UnicodeString("AVERAGE"), nErr).getTerminatedBuffer();
3057             }
3058         }
3059 
3060         // Write the Field command
3061         DoWriteCmd( sToken );
3062 
3063         // Replace tabs by </instrText><tab/><instrText>
3064         if ( nIdx > 0 ) // Is another token expected?
3065             RunText( u"\t"_ustr );
3066     }
3067 
3068     if ( bWriteRun )
3069     {
3070         m_pSerializer->endElementNS( XML_w, XML_r );
3071     }
3072 }
3073 
CmdEndField_Impl(SwTextNode const * const pNode,sal_Int32 const nPos,bool const bWriteRun)3074 void DocxAttributeOutput::CmdEndField_Impl(SwTextNode const*const pNode,
3075         sal_Int32 const nPos, bool const bWriteRun)
3076 {
3077     // Write the Field separator
3078         if ( bWriteRun )
3079         {
3080             m_pSerializer->startElementNS(XML_w, XML_r);
3081             DoWriteFieldRunProperties( pNode, nPos );
3082         }
3083 
3084         m_pSerializer->singleElementNS( XML_w, XML_fldChar,
3085               FSNS( XML_w, XML_fldCharType ), "separate" );
3086 
3087         if ( bWriteRun )
3088         {
3089             m_pSerializer->endElementNS( XML_w, XML_r );
3090         }
3091 }
3092 
3093 /// Writes properties for run that is used to separate field implementation.
3094 /// There are several runs are used:
3095 ///     <w:r>
3096 ///         <w:rPr>
3097 ///             <!-- properties written with StartRunProperties() / EndRunProperties().
3098 ///         </w:rPr>
3099 ///         <w:fldChar w:fldCharType="begin" />
3100 ///     </w:r>
3101 ///         <w:r>
3102 ///         <w:rPr>
3103 ///             <!-- properties written with DoWriteFieldRunProperties()
3104 ///         </w:rPr>
3105 ///         <w:instrText>TIME \@"HH:mm:ss"</w:instrText>
3106 ///     </w:r>
3107 ///     <w:r>
3108 ///         <w:rPr>
3109 ///             <!-- properties written with DoWriteFieldRunProperties()
3110 ///         </w:rPr>
3111 ///         <w:fldChar w:fldCharType="separate" />
3112 ///     </w:r>
3113 ///     <w:r>
3114 ///         <w:rPr>
3115 ///             <!-- properties written with DoWriteFieldRunProperties()
3116 ///         </w:rPr>
3117 ///         <w:t>14:01:13</w:t>
3118 ///         </w:r>
3119 ///     <w:r>
3120 ///         <w:rPr>
3121 ///             <!-- properties written with DoWriteFieldRunProperties()
3122 ///         </w:rPr>
3123 ///         <w:fldChar w:fldCharType="end" />
3124 ///     </w:r>
3125 /// See, tdf#38778
DoWriteFieldRunProperties(const SwTextNode * pNode,sal_Int32 nPos,bool bWriteCombChars)3126 void DocxAttributeOutput::DoWriteFieldRunProperties( const SwTextNode * pNode, sal_Int32 nPos, bool bWriteCombChars)
3127 {
3128     if (! pNode)
3129     {
3130         // nothing to do
3131         return;
3132     }
3133 
3134     m_bPreventDoubleFieldsHandling = true;
3135 
3136     {
3137         m_pSerializer->startElementNS(XML_w, XML_rPr);
3138 
3139         // 1. output webHidden flag
3140         if(GetExport().m_bHideTabLeaderAndPageNumbers && m_pHyperlinkAttrList.is() )
3141         {
3142             m_pSerializer->singleElementNS(XML_w, XML_webHidden);
3143         }
3144 
3145         // 2. find all active character properties
3146         SwWW8AttrIter aAttrIt( m_rExport, *pNode );
3147         aAttrIt.OutAttr( nPos, bWriteCombChars );
3148 
3149         // 3. write the character properties
3150         WriteCollectedRunProperties();
3151 
3152         m_pSerializer->endElementNS( XML_w, XML_rPr );
3153     }
3154 
3155     m_bPreventDoubleFieldsHandling = false;
3156 }
3157 
EndField_Impl(const SwTextNode * pNode,sal_Int32 nPos,FieldInfos & rInfos)3158 void DocxAttributeOutput::EndField_Impl( const SwTextNode* pNode, sal_Int32 nPos, FieldInfos& rInfos )
3159 {
3160     if (rInfos.eType == ww::eFORMDATE)
3161     {
3162         WriteContentControlEnd();
3163         return;
3164     }
3165     else if (rInfos.eType == ww::eFORMDROPDOWN && rInfos.pField)
3166     {
3167         // write selected item from End not Start to ensure that any bookmarks
3168         // precede it
3169         SwDropDownField const& rField(*static_cast<SwDropDownField const*>(rInfos.pField.get()));
3170         WriteSdtDropDownEnd(rField.GetSelectedItem(), rField.GetItemSequence());
3171         return;
3172     }
3173     else if (rInfos.eType == ww::eFILLIN && rInfos.pField)
3174     {
3175         SwInputField const& rField(*static_cast<SwInputField const*>(rInfos.pField.get()));
3176         if (rField.getGrabBagParams().hasElements())
3177         {
3178             WriteContentControlEnd();
3179             return;
3180         }
3181     }
3182     // The command has to be written before for the hyperlinks
3183     if ( rInfos.pField )
3184     {
3185         CmdField_Impl( pNode, nPos, rInfos, true );
3186         CmdEndField_Impl( pNode, nPos, true );
3187     }
3188 
3189     // Write the bookmark start if any
3190     if ( !m_sFieldBkm.isEmpty() )
3191     {
3192         DoWriteBookmarkTagStart(m_sFieldBkm);
3193     }
3194 
3195     if (rInfos.pField ) // For hyperlinks and TOX
3196     {
3197         // Write the Field latest value
3198         m_pSerializer->startElementNS(XML_w, XML_r);
3199         DoWriteFieldRunProperties( pNode, nPos );
3200 
3201         OUString sExpand;
3202         if(rInfos.eType == ww::eCITATION)
3203         {
3204             sExpand = static_cast<SwAuthorityField const*>(rInfos.pField.get())
3205                         ->ExpandCitation(AUTH_FIELD_TITLE, nullptr);
3206         }
3207         else if(rInfos.eType != ww::eFORMDROPDOWN)
3208         {
3209             sExpand = rInfos.pField->ExpandField(true, nullptr);
3210         }
3211         // newlines embedded in fields are 0x0B in MSO and 0x0A for us
3212         RunText(sExpand.replace(0x0A, 0x0B));
3213 
3214         m_pSerializer->endElementNS( XML_w, XML_r );
3215     }
3216 
3217     // Write the bookmark end if any
3218     if ( !m_sFieldBkm.isEmpty() )
3219     {
3220         DoWriteBookmarkTagEnd(m_nNextBookmarkId);
3221 
3222         m_nNextBookmarkId++;
3223     }
3224 
3225     // Write the Field end
3226     if ( rInfos.bClose  )
3227     {
3228         m_bWritingField = false;
3229         m_pSerializer->startElementNS(XML_w, XML_r);
3230         DoWriteFieldRunProperties( pNode, nPos );
3231         m_pSerializer->singleElementNS(XML_w, XML_fldChar, FSNS(XML_w, XML_fldCharType), "end");
3232         m_pSerializer->endElementNS( XML_w, XML_r );
3233     }
3234     // Write the ref field if a bookmark had to be set and the field
3235     // should be visible
3236     if ( !rInfos.pField )
3237     {
3238         m_sFieldBkm.clear();
3239         return;
3240     }
3241 
3242     sal_uInt16 nSubType = rInfos.pField->GetSubType( );
3243     bool bIsSetField = rInfos.pField->GetTyp( )->Which( ) == SwFieldIds::SetExp;
3244     bool bShowRef = bIsSetField && ( nSubType & nsSwExtendedSubType::SUB_INVISIBLE ) == 0;
3245 
3246     if (!bShowRef)
3247     {
3248         m_sFieldBkm.clear();
3249     }
3250 
3251     if (m_sFieldBkm.isEmpty())
3252         return;
3253 
3254     // Write the field beginning
3255     m_pSerializer->startElementNS(XML_w, XML_r);
3256     m_pSerializer->singleElementNS( XML_w, XML_fldChar,
3257         FSNS( XML_w, XML_fldCharType ), "begin" );
3258     m_pSerializer->endElementNS( XML_w, XML_r );
3259 
3260     rInfos.sCmd = FieldString( ww::eREF );
3261     rInfos.sCmd += "\"";
3262     rInfos.sCmd += m_sFieldBkm;
3263     rInfos.sCmd += "\" ";
3264 
3265     // Clean the field bookmark data to avoid infinite loop
3266     m_sFieldBkm = OUString( );
3267 
3268     // Write the end of the field
3269     EndField_Impl( pNode, nPos, rInfos );
3270 }
3271 
StartRunProperties()3272 void DocxAttributeOutput::StartRunProperties()
3273 {
3274     // postpone the output so that we can later [in EndRunProperties()]
3275     // prepend the properties before the text
3276     m_pSerializer->mark(Tag_StartRunProperties);
3277 
3278     m_pSerializer->startElementNS(XML_w, XML_rPr);
3279 
3280     if(GetExport().m_bHideTabLeaderAndPageNumbers && m_pHyperlinkAttrList.is() )
3281     {
3282         m_pSerializer->singleElementNS(XML_w, XML_webHidden);
3283     }
3284     InitCollectedRunProperties();
3285 
3286     assert( !m_oPostponedGraphic );
3287     m_oPostponedGraphic.emplace();
3288 
3289     assert( !m_oPostponedDiagrams );
3290     m_oPostponedDiagrams.emplace();
3291 
3292     assert(!m_oPostponedDMLDrawings);
3293     m_oPostponedDMLDrawings.emplace();
3294 
3295     assert( !m_oPostponedOLEs );
3296     m_oPostponedOLEs.emplace();
3297 }
3298 
InitCollectedRunProperties()3299 void DocxAttributeOutput::InitCollectedRunProperties()
3300 {
3301     m_pFontsAttrList = nullptr;
3302     m_pEastAsianLayoutAttrList = nullptr;
3303     m_pCharLangAttrList = nullptr;
3304 
3305     // Write the elements in the spec order
3306     static const sal_Int32 aOrder[] =
3307     {
3308         FSNS( XML_w, XML_rStyle ),
3309         FSNS( XML_w, XML_rFonts ),
3310         FSNS( XML_w, XML_b ),
3311         FSNS( XML_w, XML_bCs ),
3312         FSNS( XML_w, XML_i ),
3313         FSNS( XML_w, XML_iCs ),
3314         FSNS( XML_w, XML_caps ),
3315         FSNS( XML_w, XML_smallCaps ),
3316         FSNS( XML_w, XML_strike ),
3317         FSNS( XML_w, XML_dstrike ),
3318         FSNS( XML_w, XML_outline ),
3319         FSNS( XML_w, XML_shadow ),
3320         FSNS( XML_w, XML_emboss ),
3321         FSNS( XML_w, XML_imprint ),
3322         FSNS( XML_w, XML_noProof ),
3323         FSNS( XML_w, XML_snapToGrid ),
3324         FSNS( XML_w, XML_vanish ),
3325         FSNS( XML_w, XML_webHidden ),
3326         FSNS( XML_w, XML_color ),
3327         FSNS( XML_w, XML_spacing ),
3328         FSNS( XML_w, XML_w ),
3329         FSNS( XML_w, XML_kern ),
3330         FSNS( XML_w, XML_position ),
3331         FSNS( XML_w, XML_sz ),
3332         FSNS( XML_w, XML_szCs ),
3333         FSNS( XML_w, XML_highlight ),
3334         FSNS( XML_w, XML_u ),
3335         FSNS( XML_w, XML_effect ),
3336         FSNS( XML_w, XML_bdr ),
3337         FSNS( XML_w, XML_shd ),
3338         FSNS( XML_w, XML_fitText ),
3339         FSNS( XML_w, XML_vertAlign ),
3340         FSNS( XML_w, XML_rtl ),
3341         FSNS( XML_w, XML_cs ),
3342         FSNS( XML_w, XML_em ),
3343         FSNS( XML_w, XML_lang ),
3344         FSNS( XML_w, XML_eastAsianLayout ),
3345         FSNS( XML_w, XML_specVanish ),
3346         FSNS( XML_w, XML_oMath ),
3347         FSNS( XML_w, XML_rPrChange ),
3348         FSNS( XML_w, XML_del ),
3349         FSNS( XML_w, XML_ins ),
3350         FSNS( XML_w, XML_moveFrom ),
3351         FSNS( XML_w, XML_moveTo ),
3352         FSNS( XML_w14, XML_glow ),
3353         FSNS( XML_w14, XML_shadow ),
3354         FSNS( XML_w14, XML_reflection ),
3355         FSNS( XML_w14, XML_textOutline ),
3356         FSNS( XML_w14, XML_textFill ),
3357         FSNS( XML_w14, XML_scene3d ),
3358         FSNS( XML_w14, XML_props3d ),
3359         FSNS( XML_w14, XML_ligatures ),
3360         FSNS( XML_w14, XML_numForm ),
3361         FSNS( XML_w14, XML_numSpacing ),
3362         FSNS( XML_w14, XML_stylisticSets ),
3363         FSNS( XML_w14, XML_cntxtAlts ),
3364     };
3365 
3366     // postpone the output so that we can later [in EndParagraphProperties()]
3367     // prepend the properties before the run
3368     // coverity[overrun-buffer-arg : FALSE] - coverity has difficulty with css::uno::Sequence
3369     m_pSerializer->mark(Tag_InitCollectedRunProperties, comphelper::containerToSequence(aOrder));
3370 }
3371 
3372 namespace
3373 {
3374 
3375 struct NameToId
3376 {
3377     OUString  maName;
3378     sal_Int32 maId;
3379 };
3380 
3381 const NameToId constNameToIdMapping[] =
3382 {
3383     { u"glow"_ustr,         FSNS( XML_w14, XML_glow ) },
3384     { u"shadow"_ustr,       FSNS( XML_w14, XML_shadow ) },
3385     { u"reflection"_ustr,   FSNS( XML_w14, XML_reflection ) },
3386     { u"textOutline"_ustr,  FSNS( XML_w14, XML_textOutline ) },
3387     { u"textFill"_ustr,     FSNS( XML_w14, XML_textFill ) },
3388     { u"scene3d"_ustr,      FSNS( XML_w14, XML_scene3d ) },
3389     { u"props3d"_ustr,      FSNS( XML_w14, XML_props3d ) },
3390     { u"ligatures"_ustr,    FSNS( XML_w14, XML_ligatures ) },
3391     { u"numForm"_ustr,      FSNS( XML_w14, XML_numForm ) },
3392     { u"numSpacing"_ustr,   FSNS( XML_w14, XML_numSpacing ) },
3393     { u"stylisticSets"_ustr,FSNS( XML_w14, XML_stylisticSets ) },
3394     { u"cntxtAlts"_ustr,    FSNS( XML_w14, XML_cntxtAlts ) },
3395 
3396     { u"val"_ustr,          FSNS( XML_w14, XML_val ) },
3397     { u"rad"_ustr,          FSNS( XML_w14, XML_rad ) },
3398     { u"blurRad"_ustr,      FSNS( XML_w14, XML_blurRad ) },
3399     { u"stA"_ustr,          FSNS( XML_w14, XML_stA ) },
3400     { u"stPos"_ustr,        FSNS( XML_w14, XML_stPos ) },
3401     { u"endA"_ustr,         FSNS( XML_w14, XML_endA ) },
3402     { u"endPos"_ustr,       FSNS( XML_w14, XML_endPos ) },
3403     { u"dist"_ustr,         FSNS( XML_w14, XML_dist ) },
3404     { u"dir"_ustr,          FSNS( XML_w14, XML_dir ) },
3405     { u"fadeDir"_ustr,      FSNS( XML_w14, XML_fadeDir ) },
3406     { u"sx"_ustr,           FSNS( XML_w14, XML_sx ) },
3407     { u"sy"_ustr,           FSNS( XML_w14, XML_sy ) },
3408     { u"kx"_ustr,           FSNS( XML_w14, XML_kx ) },
3409     { u"ky"_ustr,           FSNS( XML_w14, XML_ky ) },
3410     { u"algn"_ustr,         FSNS( XML_w14, XML_algn ) },
3411     { u"w"_ustr,            FSNS( XML_w14, XML_w ) },
3412     { u"cap"_ustr,          FSNS( XML_w14, XML_cap ) },
3413     { u"cmpd"_ustr,         FSNS( XML_w14, XML_cmpd ) },
3414     { u"pos"_ustr,          FSNS( XML_w14, XML_pos ) },
3415     { u"ang"_ustr,          FSNS( XML_w14, XML_ang ) },
3416     { u"scaled"_ustr,       FSNS( XML_w14, XML_scaled ) },
3417     { u"path"_ustr,         FSNS( XML_w14, XML_path ) },
3418     { u"l"_ustr,            FSNS( XML_w14, XML_l ) },
3419     { u"t"_ustr,            FSNS( XML_w14, XML_t ) },
3420     { u"r"_ustr,            FSNS( XML_w14, XML_r ) },
3421     { u"b"_ustr,            FSNS( XML_w14, XML_b ) },
3422     { u"lim"_ustr,          FSNS( XML_w14, XML_lim ) },
3423     { u"prst"_ustr,         FSNS( XML_w14, XML_prst ) },
3424     { u"rig"_ustr,          FSNS( XML_w14, XML_rig ) },
3425     { u"lat"_ustr,          FSNS( XML_w14, XML_lat ) },
3426     { u"lon"_ustr,          FSNS( XML_w14, XML_lon ) },
3427     { u"rev"_ustr,          FSNS( XML_w14, XML_rev ) },
3428     { u"h"_ustr,            FSNS( XML_w14, XML_h ) },
3429     { u"extrusionH"_ustr,   FSNS( XML_w14, XML_extrusionH ) },
3430     { u"contourW"_ustr,     FSNS( XML_w14, XML_contourW ) },
3431     { u"prstMaterial"_ustr, FSNS( XML_w14, XML_prstMaterial ) },
3432     { u"id"_ustr,           FSNS( XML_w14, XML_id ) },
3433 
3434     { u"schemeClr"_ustr,    FSNS( XML_w14, XML_schemeClr ) },
3435     { u"srgbClr"_ustr,      FSNS( XML_w14, XML_srgbClr ) },
3436     { u"tint"_ustr,         FSNS( XML_w14, XML_tint ) },
3437     { u"shade"_ustr,        FSNS( XML_w14, XML_shade ) },
3438     { u"alpha"_ustr,        FSNS( XML_w14, XML_alpha ) },
3439     { u"hueMod"_ustr,       FSNS( XML_w14, XML_hueMod ) },
3440     { u"sat"_ustr,          FSNS( XML_w14, XML_sat ) },
3441     { u"satOff"_ustr,       FSNS( XML_w14, XML_satOff ) },
3442     { u"satMod"_ustr,       FSNS( XML_w14, XML_satMod ) },
3443     { u"lum"_ustr,          FSNS( XML_w14, XML_lum ) },
3444     { u"lumOff"_ustr,       FSNS( XML_w14, XML_lumOff ) },
3445     { u"lumMod"_ustr,       FSNS( XML_w14, XML_lumMod ) },
3446     { u"noFill"_ustr,       FSNS( XML_w14, XML_noFill ) },
3447     { u"solidFill"_ustr,    FSNS( XML_w14, XML_solidFill ) },
3448     { u"gradFill"_ustr,     FSNS( XML_w14, XML_gradFill ) },
3449     { u"gsLst"_ustr,        FSNS( XML_w14, XML_gsLst ) },
3450     { u"gs"_ustr,           FSNS( XML_w14, XML_gs ) },
3451     { u"pos"_ustr,          FSNS( XML_w14, XML_pos ) },
3452     { u"lin"_ustr,          FSNS( XML_w14, XML_lin ) },
3453     { u"path"_ustr,         FSNS( XML_w14, XML_path ) },
3454     { u"fillToRect"_ustr,   FSNS( XML_w14, XML_fillToRect ) },
3455     { u"prstDash"_ustr,     FSNS( XML_w14, XML_prstDash ) },
3456     { u"round"_ustr,        FSNS( XML_w14, XML_round ) },
3457     { u"bevel"_ustr,        FSNS( XML_w14, XML_bevel ) },
3458     { u"miter"_ustr,        FSNS( XML_w14, XML_miter ) },
3459     { u"camera"_ustr,       FSNS( XML_w14, XML_camera ) },
3460     { u"lightRig"_ustr,     FSNS( XML_w14, XML_lightRig ) },
3461     { u"rot"_ustr,          FSNS( XML_w14, XML_rot ) },
3462     { u"bevelT"_ustr,       FSNS( XML_w14, XML_bevelT ) },
3463     { u"bevelB"_ustr,       FSNS( XML_w14, XML_bevelB ) },
3464     { u"extrusionClr"_ustr, FSNS( XML_w14, XML_extrusionClr ) },
3465     { u"contourClr"_ustr,   FSNS( XML_w14, XML_contourClr ) },
3466     { u"styleSet"_ustr,     FSNS( XML_w14, XML_styleSet ) },
3467 };
3468 
lclGetElementIdForName(std::u16string_view rName)3469 std::optional<sal_Int32> lclGetElementIdForName(std::u16string_view rName)
3470 {
3471     for (auto const & i : constNameToIdMapping)
3472     {
3473         if (rName == i.maName)
3474         {
3475             return i.maId;
3476         }
3477     }
3478     return std::optional<sal_Int32>();
3479 }
3480 
lclProcessRecursiveGrabBag(sal_Int32 aElementId,const css::uno::Sequence<css::beans::PropertyValue> & rElements,sax_fastparser::FSHelperPtr const & pSerializer)3481 void lclProcessRecursiveGrabBag(sal_Int32 aElementId, const css::uno::Sequence<css::beans::PropertyValue>& rElements, sax_fastparser::FSHelperPtr const & pSerializer)
3482 {
3483     css::uno::Sequence<css::beans::PropertyValue> aAttributes;
3484     rtl::Reference<FastAttributeList> pAttributes = FastSerializerHelper::createAttrList();
3485 
3486     for (const auto& rElement : rElements)
3487     {
3488         if (rElement.Name == "attributes")
3489         {
3490             rElement.Value >>= aAttributes;
3491         }
3492     }
3493 
3494     for (const auto& rAttribute : aAttributes)
3495     {
3496         uno::Any aAny = rAttribute.Value;
3497         OString aValue;
3498 
3499         if(aAny.getValueType() == cppu::UnoType<sal_Int32>::get())
3500         {
3501             aValue = OString::number(aAny.get<sal_Int32>());
3502         }
3503         else if(aAny.getValueType() == cppu::UnoType<OUString>::get())
3504         {
3505             aValue =  OUStringToOString(aAny.get<OUString>(), RTL_TEXTENCODING_ASCII_US);
3506         }
3507 
3508         std::optional<sal_Int32> aSubElementId = lclGetElementIdForName(rAttribute.Name);
3509         if(aSubElementId)
3510             pAttributes->add(*aSubElementId, aValue);
3511     }
3512 
3513     pSerializer->startElement(aElementId, pAttributes);
3514 
3515     for (const auto& rElement : rElements)
3516     {
3517         css::uno::Sequence<css::beans::PropertyValue> aSumElements;
3518 
3519         std::optional<sal_Int32> aSubElementId = lclGetElementIdForName(rElement.Name);
3520         if(aSubElementId)
3521         {
3522             rElement.Value >>= aSumElements;
3523             lclProcessRecursiveGrabBag(*aSubElementId, aSumElements, pSerializer);
3524         }
3525     }
3526 
3527     pSerializer->endElement(aElementId);
3528 }
3529 
3530 }
3531 
WriteCollectedRunProperties()3532 void DocxAttributeOutput::WriteCollectedRunProperties()
3533 {
3534     // Write all differed properties
3535     if ( m_pFontsAttrList.is() )
3536     {
3537         m_pSerializer->singleElementNS( XML_w, XML_rFonts, detachFrom( m_pFontsAttrList ) );
3538     }
3539 
3540     if ( m_pColorAttrList.is() )
3541     {
3542         m_pSerializer->singleElementNS( XML_w, XML_color, m_pColorAttrList );
3543     }
3544 
3545     if ( m_pEastAsianLayoutAttrList.is() )
3546     {
3547         m_pSerializer->singleElementNS( XML_w, XML_eastAsianLayout,
3548                                         detachFrom(m_pEastAsianLayoutAttrList ) );
3549     }
3550 
3551     if ( m_pCharLangAttrList.is() )
3552     {
3553         m_pSerializer->singleElementNS( XML_w, XML_lang, detachFrom( m_pCharLangAttrList ) );
3554     }
3555 
3556     if (m_nCharTransparence != 0 && m_pColorAttrList && m_aTextEffectsGrabBag.empty())
3557     {
3558         std::string_view pVal;
3559         m_pColorAttrList->getAsView(FSNS(XML_w, XML_val), pVal);
3560         if (!pVal.empty() && pVal != "auto")
3561         {
3562             m_pSerializer->startElementNS(XML_w14, XML_textFill);
3563             m_pSerializer->startElementNS(XML_w14, XML_solidFill);
3564             m_pSerializer->startElementNS(XML_w14, XML_srgbClr, FSNS(XML_w14, XML_val), pVal.data());
3565             sal_Int32 nTransparence = m_nCharTransparence * oox::drawingml::MAX_PERCENT / 255.0;
3566             m_pSerializer->singleElementNS(XML_w14, XML_alpha, FSNS(XML_w14, XML_val), OString::number(nTransparence));
3567             m_pSerializer->endElementNS(XML_w14, XML_srgbClr);
3568             m_pSerializer->endElementNS(XML_w14, XML_solidFill);
3569             m_pSerializer->endElementNS(XML_w14, XML_textFill);
3570             m_nCharTransparence = 0;
3571         }
3572     }
3573     m_pColorAttrList.clear();
3574     for (const beans::PropertyValue & i : m_aTextEffectsGrabBag)
3575     {
3576         std::optional<sal_Int32> aElementId = lclGetElementIdForName(i.Name);
3577         if(aElementId)
3578         {
3579             uno::Sequence<beans::PropertyValue> aGrabBagSeq;
3580             i.Value >>= aGrabBagSeq;
3581             lclProcessRecursiveGrabBag(*aElementId, aGrabBagSeq, m_pSerializer);
3582         }
3583     }
3584     m_aTextEffectsGrabBag.clear();
3585 }
3586 
EndRunProperties(const SwRedlineData * pRedlineData)3587 void DocxAttributeOutput::EndRunProperties( const SwRedlineData* pRedlineData )
3588 {
3589     // Call the 'Redline' function. This will add redline (change-tracking) information that regards to run properties.
3590     // This includes changes like 'Bold', 'Underline', 'Strikethrough' etc.
3591 
3592     // If there is RedlineData present, call WriteCollectedRunProperties() for writing rPr before calling Redline().
3593     // As there will be another rPr for redline and LO might mix both.
3594     if(pRedlineData)
3595         WriteCollectedRunProperties();
3596     Redline( pRedlineData );
3597 
3598     WriteCollectedRunProperties();
3599 
3600     // Merge the marks for the ordered elements
3601     m_pSerializer->mergeTopMarks(Tag_InitCollectedRunProperties);
3602 
3603     m_pSerializer->endElementNS( XML_w, XML_rPr );
3604 
3605     // write footnotes/endnotes if we have any
3606     FootnoteEndnoteReference();
3607 
3608     WriteLineBreak();
3609 
3610     // merge the properties _before_ the run text (strictly speaking, just
3611     // after the start of the run)
3612     m_pSerializer->mergeTopMarks(Tag_StartRunProperties, sax_fastparser::MergeMarks::PREPEND);
3613 
3614     WritePostponedGraphic();
3615 
3616     WritePostponedDiagram();
3617     //We need to write w:drawing tag after the w:rPr.
3618     WritePostponedChart();
3619 
3620     //We need to write w:pict tag after the w:rPr.
3621     WritePostponedDMLDrawing();
3622 
3623     WritePostponedOLE();
3624 
3625     WritePostponedActiveXControl(true);
3626 }
3627 
GetSdtEndBefore(const SdrObject * pSdrObj)3628 void DocxAttributeOutput::GetSdtEndBefore(const SdrObject* pSdrObj)
3629 {
3630     if (!pSdrObj)
3631         return;
3632 
3633     uno::Reference<drawing::XShape> xShape(const_cast<SdrObject*>(pSdrObj)->getUnoShape());
3634     uno::Reference< beans::XPropertySet > xPropSet( xShape, uno::UNO_QUERY );
3635     if( !xPropSet.is() )
3636         return;
3637 
3638     uno::Reference< beans::XPropertySetInfo > xPropSetInfo = xPropSet->getPropertySetInfo();
3639     uno::Sequence< beans::PropertyValue > aGrabBag;
3640     if (xPropSetInfo.is() && xPropSetInfo->hasPropertyByName(u"FrameInteropGrabBag"_ustr))
3641     {
3642         xPropSet->getPropertyValue(u"FrameInteropGrabBag"_ustr) >>= aGrabBag;
3643     }
3644     else if(xPropSetInfo.is() && xPropSetInfo->hasPropertyByName(u"InteropGrabBag"_ustr))
3645     {
3646         xPropSet->getPropertyValue(u"InteropGrabBag"_ustr) >>= aGrabBag;
3647     }
3648 
3649     auto pProp = std::find_if(std::cbegin(aGrabBag), std::cend(aGrabBag),
3650         [this](const beans::PropertyValue& rProp) {
3651             return "SdtEndBefore" == rProp.Name && m_aRunSdt.m_bStartedSdt && !m_bEndCharSdt; });
3652     if (pProp != std::cend(aGrabBag))
3653         pProp->Value >>= m_bEndCharSdt;
3654 }
3655 
WritePostponedGraphic()3656 void DocxAttributeOutput::WritePostponedGraphic()
3657 {
3658     for (const auto & rPostponedDiagram : *m_oPostponedGraphic)
3659         FlyFrameGraphic(rPostponedDiagram.grfNode, rPostponedDiagram.size,
3660             nullptr, nullptr,
3661             rPostponedDiagram.pSdrObj);
3662     m_oPostponedGraphic.reset();
3663 }
3664 
WritePostponedDiagram()3665 void DocxAttributeOutput::WritePostponedDiagram()
3666 {
3667     for( const auto & rPostponedDiagram : *m_oPostponedDiagrams )
3668         m_rExport.SdrExporter().writeDiagram(rPostponedDiagram.object,
3669             *rPostponedDiagram.frame, m_anchorId++);
3670     m_oPostponedDiagrams.reset();
3671 }
3672 
FootnoteEndnoteRefTag()3673 bool DocxAttributeOutput::FootnoteEndnoteRefTag()
3674 {
3675     if( m_footnoteEndnoteRefTag == 0 )
3676         return false;
3677 
3678     // output the character style for MS Word's benefit
3679     const SwEndNoteInfo& rInfo = m_footnoteEndnoteRefTag == XML_footnoteRef ?
3680         m_rExport.m_rDoc.GetFootnoteInfo() : m_rExport.m_rDoc.GetEndNoteInfo();
3681     const SwCharFormat* pCharFormat = rInfo.GetCharFormat( m_rExport.m_rDoc );
3682     if ( pCharFormat )
3683     {
3684         const OString aStyleId(m_rExport.m_pStyles->GetStyleId(m_rExport.GetId(pCharFormat)));
3685         m_pSerializer->startElementNS(XML_w, XML_rPr);
3686         m_pSerializer->singleElementNS(XML_w, XML_rStyle, FSNS(XML_w, XML_val), aStyleId);
3687         m_pSerializer->endElementNS( XML_w, XML_rPr );
3688     }
3689 
3690     if (m_footnoteCustomLabel.isEmpty())
3691         m_pSerializer->singleElementNS(XML_w, m_footnoteEndnoteRefTag);
3692     else
3693         RunText(m_footnoteCustomLabel);
3694     m_footnoteEndnoteRefTag = 0;
3695     return true;
3696 }
3697 
3698 /** Output sal_Unicode* as a run text (<t>the text</t>).
3699 
3700     When bMove is true, update rBegin to point _after_ the end of the text +
3701     1, meaning that it skips one character after the text.  This is to make
3702     the switch in DocxAttributeOutput::RunText() nicer ;-)
3703  */
impl_WriteRunText(FSHelperPtr const & pSerializer,sal_Int32 nTextToken,const sal_Unicode * & rBegin,const sal_Unicode * pEnd,bool bMove=true,const OUString & rSymbolFont=OUString ())3704 static bool impl_WriteRunText( FSHelperPtr const & pSerializer, sal_Int32 nTextToken,
3705         const sal_Unicode* &rBegin, const sal_Unicode* pEnd, bool bMove = true,
3706         const OUString& rSymbolFont = OUString() )
3707 {
3708     const sal_Unicode *pBegin = rBegin;
3709 
3710     // skip one character after the end
3711     if ( bMove )
3712         rBegin = pEnd + 1;
3713 
3714     if ( pBegin >= pEnd )
3715         return false; // we want to write at least one character
3716 
3717     bool bIsSymbol = !rSymbolFont.isEmpty();
3718 
3719     std::u16string_view aView( pBegin, pEnd - pBegin );
3720     if (bIsSymbol)
3721     {
3722         for (char16_t aChar : aView)
3723         {
3724             pSerializer->singleElementNS(XML_w, XML_sym,
3725                 FSNS(XML_w, XML_font), rSymbolFont,
3726                 FSNS(XML_w, XML_char), OString::number(aChar, 16));
3727         }
3728     }
3729     else
3730     {
3731         // we have to add 'preserve' when starting/ending with space
3732         if ( *pBegin == ' ' || *( pEnd - 1 ) == ' ' )
3733             pSerializer->startElementNS(XML_w, nTextToken, FSNS(XML_xml, XML_space), "preserve");
3734         else
3735             pSerializer->startElementNS(XML_w, nTextToken);
3736 
3737         pSerializer->writeEscaped( aView );
3738         pSerializer->endElementNS( XML_w, nTextToken );
3739     }
3740 
3741     return true;
3742 }
3743 
RunText(const OUString & rText,rtl_TextEncoding,const OUString & rSymbolFont)3744 void DocxAttributeOutput::RunText( const OUString& rText, rtl_TextEncoding /*eCharSet*/, const OUString& rSymbolFont )
3745 {
3746     if( m_closeHyperlinkInThisRun )
3747     {
3748         m_closeHyperlinkInPreviousRun = true;
3749     }
3750     m_bRunTextIsOn = true;
3751     // one text can be split into more <w:t>blah</w:t>'s by line breaks etc.
3752     const sal_Unicode *pBegin = rText.getStr();
3753     const sal_Unicode *pEnd = pBegin + rText.getLength();
3754 
3755     // the text run is usually XML_t, with the exception of the deleted (and not moved) text
3756     sal_Int32 nTextToken = XML_t;
3757 
3758     bool bMoved = m_pRedlineData && m_pRedlineData->IsMoved() &&
3759        // tdf#150166 save tracked moving around TOC as w:ins, w:del
3760        SwDoc::GetCurTOX(*m_rExport.m_pCurPam->GetPoint()) == nullptr;
3761 
3762     if ( m_pRedlineData && m_pRedlineData->GetType() == RedlineType::Delete && !bMoved )
3763     {
3764         nTextToken = XML_delText;
3765     }
3766 
3767     sal_Unicode prevUnicode = *pBegin;
3768 
3769     for ( const sal_Unicode *pIt = pBegin; pIt < pEnd; ++pIt )
3770     {
3771         switch ( *pIt )
3772         {
3773             case 0x09: // tab
3774                 impl_WriteRunText( m_pSerializer, nTextToken, pBegin, pIt );
3775                 m_pSerializer->singleElementNS(XML_w, XML_tab);
3776                 prevUnicode = *pIt;
3777                 break;
3778             case 0x0b: // line break
3779             case static_cast<sal_Unicode>(text::ControlCharacter::LINE_BREAK):
3780                 {
3781                     if (impl_WriteRunText( m_pSerializer, nTextToken, pBegin, pIt ) || prevUnicode < 0x0020)
3782                     {
3783                         m_pSerializer->singleElementNS(XML_w, XML_br);
3784                         prevUnicode = *pIt;
3785                     }
3786                 }
3787                 break;
3788             case 0x1E: //non-breaking hyphen
3789                 impl_WriteRunText( m_pSerializer, nTextToken, pBegin, pIt );
3790                 m_pSerializer->singleElementNS(XML_w, XML_noBreakHyphen);
3791                 prevUnicode = *pIt;
3792                 break;
3793             case 0x1F: //soft (on demand) hyphen
3794                 impl_WriteRunText( m_pSerializer, nTextToken, pBegin, pIt );
3795                 m_pSerializer->singleElementNS(XML_w, XML_softHyphen);
3796                 prevUnicode = *pIt;
3797                 break;
3798             default:
3799                 if ( *pIt < 0x0020 ) // filter out the control codes
3800                 {
3801                     impl_WriteRunText( m_pSerializer, nTextToken, pBegin, pIt );
3802                     SAL_INFO("sw.ww8", "Ignored control code in a text run: " << unsigned(*pIt) );
3803                 }
3804                 prevUnicode = *pIt;
3805                 break;
3806         }
3807     }
3808 
3809     impl_WriteRunText( m_pSerializer, nTextToken, pBegin, pEnd, false, rSymbolFont );
3810 }
3811 
RawText(const OUString & rText,rtl_TextEncoding)3812 void DocxAttributeOutput::RawText(const OUString& rText, rtl_TextEncoding /*eCharSet*/)
3813 {
3814     m_sRawText = rText;
3815 }
3816 
StartRuby(const SwTextNode & rNode,sal_Int32 nPos,const SwFormatRuby & rRuby)3817 void DocxAttributeOutput::StartRuby( const SwTextNode& rNode, sal_Int32 nPos, const SwFormatRuby& rRuby )
3818 {
3819     WW8Ruby aWW8Ruby( rNode, rRuby, GetExport() );
3820     SAL_INFO("sw.ww8", "TODO DocxAttributeOutput::StartRuby( const SwTextNode& rNode, const SwFormatRuby& rRuby )" );
3821     EndRun( &rNode, nPos, -1 ); // end run before starting ruby to avoid nested runs, and overlap
3822     assert(!m_closeHyperlinkInThisRun); // check that no hyperlink overlaps ruby
3823     assert(!m_closeHyperlinkInPreviousRun);
3824     m_pSerializer->startElementNS(XML_w, XML_r);
3825     m_pSerializer->startElementNS(XML_w, XML_ruby);
3826     m_pSerializer->startElementNS(XML_w, XML_rubyPr);
3827 
3828     m_pSerializer->singleElementNS( XML_w, XML_rubyAlign,
3829             FSNS( XML_w, XML_val ), lclConvertWW8JCToOOXMLRubyAlign(aWW8Ruby.GetJC()) );
3830     sal_uInt32   nHps = (aWW8Ruby.GetRubyHeight() + 5) / 10;
3831     sal_uInt32   nHpsBaseText = (aWW8Ruby.GetBaseHeight() + 5) / 10;
3832     m_pSerializer->singleElementNS(XML_w, XML_hps, FSNS(XML_w, XML_val), OString::number(nHps));
3833 
3834     m_pSerializer->singleElementNS( XML_w, XML_hpsRaise,
3835             FSNS( XML_w, XML_val ), OString::number(nHpsBaseText) );
3836 
3837     m_pSerializer->singleElementNS( XML_w, XML_hpsBaseText,
3838             FSNS( XML_w, XML_val ), OString::number(nHpsBaseText) );
3839 
3840     lang::Locale aLocale( SwBreakIt::Get()->GetLocale(
3841                 rNode.GetLang( nPos ) ) );
3842     OUString sLang( LanguageTag::convertToBcp47( aLocale) );
3843     m_pSerializer->singleElementNS(XML_w, XML_lid, FSNS(XML_w, XML_val), sLang);
3844 
3845     m_pSerializer->endElementNS( XML_w, XML_rubyPr );
3846 
3847     m_pSerializer->startElementNS(XML_w, XML_rt);
3848     StartRun( nullptr, nPos );
3849     StartRunProperties( );
3850 
3851     if (rRuby.GetTextRuby() && rRuby.GetTextRuby()->GetCharFormat())
3852     {
3853         const SwCharFormat* pFormat = rRuby.GetTextRuby()->GetCharFormat();
3854         sal_uInt16 nScript = g_pBreakIt->GetBreakIter()->getScriptType(rRuby.GetText(), 0);
3855         TypedWhichId<SvxFontItem> nWhichFont = (nScript == i18n::ScriptType::LATIN) ?  RES_CHRATR_FONT : RES_CHRATR_CJK_FONT;
3856         TypedWhichId<SvxFontHeightItem> nWhichFontSize = (nScript == i18n::ScriptType::LATIN) ?  RES_CHRATR_FONTSIZE : RES_CHRATR_CJK_FONTSIZE;
3857 
3858         CharFont(pFormat->GetFormatAttr(nWhichFont));
3859         CharFontSize(pFormat->GetFormatAttr(nWhichFontSize));
3860         CharFontSize(pFormat->GetFormatAttr(RES_CHRATR_CTL_FONTSIZE));
3861     }
3862 
3863     EndRunProperties( nullptr );
3864     RunText( rRuby.GetText( ) );
3865     EndRun( &rNode, nPos, -1 );
3866     m_pSerializer->endElementNS( XML_w, XML_rt );
3867 
3868     m_pSerializer->startElementNS(XML_w, XML_rubyBase);
3869     StartRun( nullptr, nPos );
3870 }
3871 
EndRuby(const SwTextNode & rNode,sal_Int32 nPos)3872 void DocxAttributeOutput::EndRuby(const SwTextNode& rNode, sal_Int32 nPos)
3873 {
3874     SAL_INFO("sw.ww8", "TODO DocxAttributeOutput::EndRuby()" );
3875     EndRun( &rNode, nPos, -1 );
3876     m_pSerializer->endElementNS( XML_w, XML_rubyBase );
3877     m_pSerializer->endElementNS( XML_w, XML_ruby );
3878     m_pSerializer->endElementNS( XML_w, XML_r );
3879     StartRun(nullptr, nPos); // open Run again so OutputTextNode loop can close it
3880 }
3881 
AnalyzeURL(const OUString & rUrl,const OUString & rTarget,OUString * pLinkURL,OUString * pMark)3882 bool DocxAttributeOutput::AnalyzeURL( const OUString& rUrl, const OUString& rTarget, OUString* pLinkURL, OUString* pMark )
3883 {
3884     bool bBookMarkOnly = AttributeOutputBase::AnalyzeURL( rUrl, rTarget, pLinkURL, pMark );
3885     if (bBookMarkOnly)
3886         *pMark = GetExport().BookmarkToWord(*pMark);
3887 
3888     if (!pMark->isEmpty() && (bBookMarkOnly || rTarget.isEmpty()))
3889     {
3890         OUString sURL = *pLinkURL;
3891 
3892         if ( bBookMarkOnly )
3893             sURL = FieldString( ww::eHYPERLINK );
3894         else
3895             sURL = FieldString( ww::eHYPERLINK ) + "\"" + sURL + "\"";
3896 
3897         sURL += " \\l \"" + *pMark + "\"";
3898 
3899         if ( !rTarget.isEmpty() )
3900             sURL += " \\n " + rTarget;
3901 
3902         *pLinkURL = sURL;
3903     }
3904 
3905     return bBookMarkOnly;
3906 }
3907 
WriteBookmarkInActParagraph(const OUString & rName,sal_Int32 nFirstRunPos,sal_Int32 nLastRunPos)3908 void DocxAttributeOutput::WriteBookmarkInActParagraph( const OUString& rName, sal_Int32 nFirstRunPos, sal_Int32 nLastRunPos )
3909 {
3910     m_aBookmarksOfParagraphStart.insert(std::pair<sal_Int32, OUString>(nFirstRunPos, rName));
3911     m_aBookmarksOfParagraphEnd.insert(std::pair<sal_Int32, OUString>(nLastRunPos, rName));
3912 }
3913 
StartURL(const OUString & rUrl,const OUString & rTarget,const OUString & rName)3914 bool DocxAttributeOutput::StartURL(const OUString& rUrl, const OUString& rTarget, const OUString& rName)
3915 {
3916     OUString sMark;
3917     OUString sUrl;
3918 
3919     bool bBookmarkOnly = AnalyzeURL( rUrl, rTarget, &sUrl, &sMark );
3920 
3921     m_hyperLinkAnchor = sMark;
3922 
3923     if (!sMark.isEmpty() && !bBookmarkOnly && rTarget.isEmpty())
3924     {
3925         m_rExport.OutputField( nullptr, ww::eHYPERLINK, sUrl );
3926     }
3927     else
3928     {
3929         // Output a hyperlink XML element
3930         m_pHyperlinkAttrList = FastSerializerHelper::createAttrList();
3931 
3932         if ( !bBookmarkOnly )
3933         {
3934             OUString sId = GetExport().GetFilter().addRelation( m_pSerializer->getOutputStream(),
3935                         oox::getRelationship(Relationship::HYPERLINK),
3936                         sUrl, true );
3937 
3938             m_pHyperlinkAttrList->add(FSNS(XML_r, XML_id), sId);
3939             if (!sMark.isEmpty())
3940             {
3941                 sMark = sMark.replace(' ', '_');
3942                 m_pHyperlinkAttrList->add(FSNS(XML_w, XML_anchor), sMark);
3943             }
3944         }
3945         else
3946         {
3947             // Is this a link to a sequence? Then try to replace that with a
3948             // normal bookmark, as Word won't understand our special
3949             // <seqname>!<index>|sequence syntax.
3950             if (sMark.endsWith("|sequence"))
3951             {
3952                 sal_Int32 nPos = sMark.indexOf('!');
3953                 if (nPos != -1)
3954                 {
3955                     // Extract <seqname>, the field instruction text has the name quoted.
3956                     OUString aSequenceName = sMark.copy(0, nPos);
3957                     // Extract <index>.
3958                     sal_uInt32 nIndex = o3tl::toUInt32(sMark.subView(nPos + 1, sMark.getLength() - nPos - sizeof("|sequence")));
3959                     auto it = m_aSeqBookmarksNames.find(aSequenceName);
3960                     if (it != m_aSeqBookmarksNames.end())
3961                     {
3962                         std::vector<OUString>& rNames = it->second;
3963                         if (rNames.size() > nIndex)
3964                             // We know the bookmark name for this sequence and this index, do the replacement.
3965                             sMark = rNames[nIndex];
3966                     }
3967                 }
3968             }
3969             else if (sMark.endsWith("|toxmark"))
3970             {
3971                 if (auto const it = GetExport().m_TOXMarkBookmarksByURL.find(sMark);
3972                     it != GetExport().m_TOXMarkBookmarksByURL.end())
3973                 {
3974                     sMark = it->second;
3975                 }
3976             }
3977             // Spaces are prohibited in bookmark name.
3978             sMark = sMark.replace(' ', '_');
3979             m_pHyperlinkAttrList->add( FSNS( XML_w, XML_anchor ), sMark );
3980         }
3981 
3982         if ( !rTarget.isEmpty() )
3983         {
3984             m_pHyperlinkAttrList->add(FSNS(XML_w, XML_tgtFrame), rTarget);
3985         }
3986         else if (!rName.isEmpty())
3987         {
3988             m_pHyperlinkAttrList->add(FSNS(XML_w, XML_tooltip), rName);
3989         }
3990     }
3991 
3992     return true;
3993 }
3994 
EndURL(bool const)3995 bool DocxAttributeOutput::EndURL(bool const)
3996 {
3997     m_closeHyperlinkInThisRun = true;
3998     if (m_nHyperLinkCount.back() > 0 && !m_hyperLinkAnchor.isEmpty()
3999         && m_hyperLinkAnchor.startsWith("_Toc"))
4000     {
4001         m_endPageRef = true;
4002     }
4003     return true;
4004 }
4005 
FieldVanish(const OUString & rText,ww::eField const eType,OUString const * const pBookmarkName)4006 void DocxAttributeOutput::FieldVanish(const OUString& rText,
4007         ww::eField const eType, OUString const*const pBookmarkName)
4008 {
4009     WriteField_Impl(nullptr, eType, rText, FieldFlags::All, pBookmarkName);
4010 }
4011 
4012 // The difference between 'Redline' and 'StartRedline'+'EndRedline' is that:
4013 // 'Redline' is used for tracked changes of formatting information of a run like Bold, Underline. (the '<w:rPrChange>' is inside the 'run' node)
4014 // 'StartRedline' is used to output tracked changes of run insertion and deletion (the run is inside the '<w:ins>' node)
Redline(const SwRedlineData * pRedlineData)4015 void DocxAttributeOutput::Redline( const SwRedlineData* pRedlineData)
4016 {
4017     if ( !pRedlineData )
4018         return;
4019 
4020     bool bRemovePersonalInfo = SvtSecurityOptions::IsOptionSet(
4021         SvtSecurityOptions::EOption::DocWarnRemovePersonalInfo ) && !SvtSecurityOptions::IsOptionSet(
4022             SvtSecurityOptions::EOption::DocWarnKeepRedlineInfo);
4023 
4024     OString aId( OString::number( pRedlineData->GetSeqNo() ) );
4025     const OUString &rAuthor( SW_MOD()->GetRedlineAuthor( pRedlineData->GetAuthor() ) );
4026     const DateTime aDateTime = pRedlineData->GetTimeStamp();
4027     bool bNoDate = bRemovePersonalInfo ||
4028         ( aDateTime.GetYear() == 1970 && aDateTime.GetMonth() == 1 && aDateTime.GetDay() == 1 );
4029 
4030     switch( pRedlineData->GetType() )
4031     {
4032     case RedlineType::Insert:
4033         break;
4034 
4035     case RedlineType::Delete:
4036         break;
4037 
4038     case RedlineType::Format:
4039     {
4040         rtl::Reference<sax_fastparser::FastAttributeList> pAttributeList
4041             = sax_fastparser::FastSerializerHelper::createAttrList();
4042 
4043         pAttributeList->add(FSNS( XML_w, XML_id ), aId);
4044         pAttributeList->add(FSNS( XML_w, XML_author ), bRemovePersonalInfo
4045                     ? "Author" + OString::number( GetExport().GetInfoID(rAuthor) )
4046                     : rAuthor.toUtf8());
4047         if (!bNoDate)
4048             pAttributeList->add(FSNS( XML_w, XML_date ), DateTimeToOString( aDateTime ));
4049         m_pSerializer->startElementNS( XML_w, XML_rPrChange, pAttributeList );
4050 
4051         // Check if there is any extra data stored in the redline object
4052         if (pRedlineData->GetExtraData())
4053         {
4054             const SwRedlineExtraData* pExtraData = pRedlineData->GetExtraData();
4055             const SwRedlineExtraData_FormatColl* pFormattingChanges = dynamic_cast<const SwRedlineExtraData_FormatColl*>(pExtraData);
4056 
4057             // Check if the extra data is of type 'formatting changes'
4058             if (pFormattingChanges)
4059             {
4060                  // Get the item set that holds all the changes properties
4061                 const SfxItemSet *pChangesSet = pFormattingChanges->GetItemSet();
4062                 if (pChangesSet)
4063                 {
4064                     m_pSerializer->mark(Tag_Redline_1);
4065 
4066                     m_pSerializer->startElementNS(XML_w, XML_rPr);
4067 
4068                     // Output the redline item set
4069                     if (pChangesSet)
4070                         m_rExport.OutputItemSet( *pChangesSet, false, true, i18n::ScriptType::LATIN, m_rExport.m_bExportModeRTF );
4071 
4072                     m_pSerializer->endElementNS( XML_w, XML_rPr );
4073 
4074                     m_pSerializer->mergeTopMarks(Tag_Redline_1, sax_fastparser::MergeMarks::PREPEND);
4075                 }
4076             }
4077         }
4078 
4079         m_pSerializer->endElementNS( XML_w, XML_rPrChange );
4080         break;
4081     }
4082     case RedlineType::ParagraphFormat:
4083     {
4084         rtl::Reference<sax_fastparser::FastAttributeList> pAttributeList
4085             = sax_fastparser::FastSerializerHelper::createAttrList();
4086 
4087         pAttributeList->add(FSNS( XML_w, XML_id ), aId);
4088         pAttributeList->add(FSNS( XML_w, XML_author ), bRemovePersonalInfo
4089                     ? "Author" + OString::number( GetExport().GetInfoID(rAuthor) )
4090                     : rAuthor.toUtf8());
4091         if (!bNoDate)
4092             pAttributeList->add(FSNS( XML_w, XML_date ), DateTimeToOString( aDateTime ));
4093         m_pSerializer->startElementNS( XML_w, XML_pPrChange, pAttributeList );
4094 
4095         // Check if there is any extra data stored in the redline object
4096         if (pRedlineData->GetExtraData())
4097         {
4098             const SwRedlineExtraData* pExtraData = pRedlineData->GetExtraData();
4099             const SwRedlineExtraData_FormatColl* pFormattingChanges = dynamic_cast<const SwRedlineExtraData_FormatColl*>(pExtraData);
4100 
4101             // Check if the extra data is of type 'formatting changes'
4102             if (pFormattingChanges)
4103             {
4104                 // Get the item set that holds all the changes properties
4105                 const SfxItemSet *pChangesSet = pFormattingChanges->GetItemSet();
4106                 const OUString & sParaStyleName = pFormattingChanges->GetFormatName();
4107                 if (pChangesSet || !sParaStyleName.isEmpty())
4108                 {
4109                     m_pSerializer->mark(Tag_Redline_2);
4110 
4111                     m_pSerializer->startElementNS(XML_w, XML_pPr);
4112 
4113                     if (!sParaStyleName.isEmpty())
4114                     {
4115                         OString sStyleName;
4116                         if (auto format = m_rExport.m_rDoc.FindTextFormatCollByName(sParaStyleName))
4117                             if (auto slot = m_rExport.m_pStyles->GetSlot(format); slot != 0xfff)
4118                                 sStyleName = m_rExport.m_pStyles->GetStyleId(slot);
4119                         // The resolved style name can be empty at this point, sParaStyleName can be
4120                         // an arbitrary string from the original document.
4121                         // Note that Word does *not* roundtrip unknown style names in redlines!
4122                         if (sStyleName.isEmpty())
4123                             sStyleName = MSWordStyles::CreateStyleId(sParaStyleName);
4124                         if (!sStyleName.isEmpty())
4125                             m_pSerializer->singleElementNS(XML_w, XML_pStyle, FSNS(XML_w, XML_val), sStyleName);
4126                     }
4127 
4128                     // The 'm_rExport.SdrExporter().getFlyAttrList()', 'm_pParagraphSpacingAttrList' are used to hold information
4129                     // that should be collected by different properties in the core, and are all flushed together
4130                     // to the DOCX when the function 'WriteCollectedParagraphProperties' gets called.
4131                     // So we need to store the current status of these lists, so that we can revert back to them when
4132                     // we are done exporting the redline attributes.
4133                     auto pFlyAttrList_Original(detachFrom(m_rExport.SdrExporter().getFlyAttrList()));
4134                     auto pLRSpaceAttrList_Original(detachFrom(m_pLRSpaceAttrList));
4135                     auto pParagraphSpacingAttrList_Original(detachFrom(m_pParagraphSpacingAttrList));
4136 
4137                     // Output the redline item set
4138                     if (pChangesSet)
4139                         m_rExport.OutputItemSet( *pChangesSet, true, false, i18n::ScriptType::LATIN, m_rExport.m_bExportModeRTF );
4140 
4141                     // Write the collected paragraph properties that are stored in 'm_rExport.SdrExporter().getFlyAttrList()', 'm_pParagraphSpacingAttrList'
4142                     WriteCollectedParagraphProperties();
4143 
4144                     // Revert back the original values that were stored in 'm_rExport.SdrExporter().getFlyAttrList()', 'm_pParagraphSpacingAttrList'
4145                     m_rExport.SdrExporter().getFlyAttrList() = std::move(pFlyAttrList_Original);
4146                     m_pLRSpaceAttrList = std::move(pLRSpaceAttrList_Original);
4147                     m_pParagraphSpacingAttrList = std::move(pParagraphSpacingAttrList_Original);
4148 
4149                     m_pSerializer->endElementNS( XML_w, XML_pPr );
4150 
4151                     m_pSerializer->mergeTopMarks(Tag_Redline_2, sax_fastparser::MergeMarks::PREPEND);
4152                 }
4153             }
4154         }
4155         m_pSerializer->endElementNS( XML_w, XML_pPrChange );
4156         break;
4157     }
4158     default:
4159         SAL_WARN("sw.ww8", "Unhandled redline type for export " << SwRedlineTypeToOUString(pRedlineData->GetType()));
4160         break;
4161     }
4162 }
4163 
4164 // The difference between 'Redline' and 'StartRedline'+'EndRedline' is that:
4165 // 'Redline' is used for tracked changes of formatting information of a run like Bold, Underline. (the '<w:rPrChange>' is inside the 'run' node)
4166 // 'StartRedline' is used to output tracked changes of run insertion and deletion (the run is inside the '<w:ins>' node)
StartRedline(const SwRedlineData * pRedlineData,bool bLastRun)4167 void DocxAttributeOutput::StartRedline( const SwRedlineData * pRedlineData, bool bLastRun )
4168 {
4169     if ( !pRedlineData )
4170         return;
4171 
4172     // write out stack of this redline recursively (first the oldest)
4173     if ( !bLastRun )
4174         StartRedline( pRedlineData->Next(), false );
4175 
4176     OString aId( OString::number( m_nRedlineId++ ) );
4177 
4178     bool bRemovePersonalInfo = SvtSecurityOptions::IsOptionSet(
4179         SvtSecurityOptions::EOption::DocWarnRemovePersonalInfo ) && !SvtSecurityOptions::IsOptionSet(
4180             SvtSecurityOptions::EOption::DocWarnKeepRedlineInfo);
4181 
4182     const OUString &rAuthor( SW_MOD()->GetRedlineAuthor( pRedlineData->GetAuthor() ) );
4183     OString aAuthor( OUStringToOString( bRemovePersonalInfo
4184                         ? "Author" + OUString::number( GetExport().GetInfoID(rAuthor) )
4185                         : rAuthor, RTL_TEXTENCODING_UTF8 ) );
4186 
4187     const DateTime aDateTime = pRedlineData->GetTimeStamp();
4188     bool bNoDate = bRemovePersonalInfo ||
4189         ( aDateTime.GetYear() == 1970 && aDateTime.GetMonth() == 1 && aDateTime.GetDay() == 1 );
4190     bool bMoved = pRedlineData->IsMoved() &&
4191        // tdf#150166 save tracked moving around TOC as w:ins, w:del
4192        SwDoc::GetCurTOX(*m_rExport.m_pCurPam->GetPoint()) == nullptr;
4193     switch ( pRedlineData->GetType() )
4194     {
4195         case RedlineType::Insert:
4196         case RedlineType::Delete:
4197         {
4198             sal_Int32 eElement = RedlineType::Insert == pRedlineData->GetType()
4199                 ? ( bMoved ? XML_moveTo : XML_ins )
4200                 : ( bMoved ? XML_moveFrom : XML_del );
4201             if ( bNoDate )
4202                 m_pSerializer->startElementNS( XML_w, eElement,
4203                     FSNS( XML_w, XML_id ), aId,
4204                     FSNS( XML_w, XML_author ), aAuthor );
4205             else
4206                 m_pSerializer->startElementNS( XML_w, eElement,
4207                     FSNS( XML_w, XML_id ), aId,
4208                     FSNS( XML_w, XML_author ), aAuthor,
4209                     FSNS( XML_w, XML_date ), DateTimeToOString( aDateTime ) );
4210             break;
4211         }
4212         case RedlineType::Format:
4213             SAL_INFO("sw.ww8", "TODO DocxAttributeOutput::StartRedline()" );
4214             break;
4215         default:
4216             break;
4217     }
4218 }
4219 
EndRedline(const SwRedlineData * pRedlineData,bool bLastRun)4220 void DocxAttributeOutput::EndRedline( const SwRedlineData * pRedlineData, bool bLastRun )
4221 {
4222     if ( !pRedlineData || m_bWritingField )
4223         return;
4224 
4225     bool bMoved = pRedlineData->IsMoved() &&
4226        // tdf#150166 save tracked moving around TOC as w:ins, w:del
4227        SwDoc::GetCurTOX(*m_rExport.m_pCurPam->GetPoint()) == nullptr;
4228     switch ( pRedlineData->GetType() )
4229     {
4230         case RedlineType::Insert:
4231             m_pSerializer->endElementNS( XML_w, bMoved ? XML_moveTo : XML_ins );
4232             break;
4233 
4234         case RedlineType::Delete:
4235             m_pSerializer->endElementNS( XML_w, bMoved ? XML_moveFrom : XML_del );
4236             break;
4237 
4238         case RedlineType::Format:
4239             SAL_INFO("sw.ww8", "TODO DocxAttributeOutput::EndRedline()" );
4240             break;
4241         default:
4242             break;
4243     }
4244 
4245     // write out stack of this redline recursively (first the newest)
4246     if ( !bLastRun )
4247         EndRedline( pRedlineData->Next(), false );
4248 }
4249 
FormatDrop(const SwTextNode &,const SwFormatDrop &,sal_uInt16,ww8::WW8TableNodeInfo::Pointer_t,ww8::WW8TableNodeInfoInner::Pointer_t)4250 void DocxAttributeOutput::FormatDrop( const SwTextNode& /*rNode*/, const SwFormatDrop& /*rSwFormatDrop*/, sal_uInt16 /*nStyle*/, ww8::WW8TableNodeInfo::Pointer_t /*pTextNodeInfo*/, ww8::WW8TableNodeInfoInner::Pointer_t )
4251 {
4252     SAL_INFO("sw.ww8", "TODO DocxAttributeOutput::FormatDrop( const SwTextNode& rNode, const SwFormatDrop& rSwFormatDrop, sal_uInt16 nStyle )" );
4253 }
4254 
ParagraphStyle(sal_uInt16 nStyle)4255 void DocxAttributeOutput::ParagraphStyle( sal_uInt16 nStyle )
4256 {
4257     OString aStyleId(m_rExport.m_pStyles->GetStyleId(nStyle));
4258 
4259     m_pSerializer->singleElementNS(XML_w, XML_pStyle, FSNS(XML_w, XML_val), aStyleId);
4260 }
4261 
impl_borderLine(FSHelperPtr const & pSerializer,sal_Int32 elementToken,const SvxBorderLine * pBorderLine,sal_uInt16 nDist,bool bWriteShadow,const table::BorderLine2 * pStyleProps=nullptr)4262 static void impl_borderLine( FSHelperPtr const & pSerializer, sal_Int32 elementToken, const SvxBorderLine* pBorderLine, sal_uInt16 nDist,
4263                              bool bWriteShadow, const table::BorderLine2* pStyleProps = nullptr)
4264 {
4265     // Compute val attribute value
4266     // Can be one of:
4267     //      single, double,
4268     //      basicWideOutline, basicWideInline
4269     // OOXml also supports those types of borders, but we'll try to play with the first ones.
4270     //      thickThinMediumGap, thickThinLargeGap, thickThinSmallGap
4271     //      thinThickLargeGap, thinThickMediumGap, thinThickSmallGap
4272     const char* pVal = "nil";
4273     if ( pBorderLine && !pBorderLine->isEmpty( ) )
4274     {
4275         switch (pBorderLine->GetBorderLineStyle())
4276         {
4277             case SvxBorderLineStyle::SOLID:
4278                 pVal = "single";
4279                 break;
4280             case SvxBorderLineStyle::DOTTED:
4281                 pVal = "dotted";
4282                 break;
4283             case SvxBorderLineStyle::DASHED:
4284                 pVal = "dashed";
4285                 break;
4286             case SvxBorderLineStyle::DOUBLE:
4287             case SvxBorderLineStyle::DOUBLE_THIN:
4288                 pVal = "double";
4289                 break;
4290             case SvxBorderLineStyle::THINTHICK_SMALLGAP:
4291                 pVal = "thinThickSmallGap";
4292                 break;
4293             case SvxBorderLineStyle::THINTHICK_MEDIUMGAP:
4294                 pVal = "thinThickMediumGap";
4295                 break;
4296             case SvxBorderLineStyle::THINTHICK_LARGEGAP:
4297                 pVal = "thinThickLargeGap";
4298                 break;
4299             case SvxBorderLineStyle::THICKTHIN_SMALLGAP:
4300                 pVal = "thickThinSmallGap";
4301                 break;
4302             case SvxBorderLineStyle::THICKTHIN_MEDIUMGAP:
4303                 pVal = "thickThinMediumGap";
4304                 break;
4305             case SvxBorderLineStyle::THICKTHIN_LARGEGAP:
4306                 pVal = "thickThinLargeGap";
4307                 break;
4308             case SvxBorderLineStyle::EMBOSSED:
4309                 pVal = "threeDEmboss";
4310                 break;
4311             case SvxBorderLineStyle::ENGRAVED:
4312                 pVal = "threeDEngrave";
4313                 break;
4314             case SvxBorderLineStyle::OUTSET:
4315                 pVal = "outset";
4316                 break;
4317             case SvxBorderLineStyle::INSET:
4318                 pVal = "inset";
4319                 break;
4320             case SvxBorderLineStyle::FINE_DASHED:
4321                 pVal = "dashSmallGap";
4322                 break;
4323             case SvxBorderLineStyle::DASH_DOT:
4324                 pVal = "dotDash";
4325                 break;
4326             case SvxBorderLineStyle::DASH_DOT_DOT:
4327                 pVal = "dotDotDash";
4328                 break;
4329             case SvxBorderLineStyle::NONE:
4330             default:
4331                 break;
4332         }
4333     }
4334     else if (!pStyleProps || !pStyleProps->LineWidth)
4335         // no line, and no line set by the style either:
4336         // there is no need to write the property
4337         return;
4338 
4339     // compare the properties with the theme properties before writing them:
4340     // if they are equal, it means that they were style-defined and there is
4341     // no need to write them.
4342     if (pStyleProps && pBorderLine && !pBorderLine->isEmpty()
4343         && pBorderLine->GetBorderLineStyle()
4344                == static_cast<SvxBorderLineStyle>(pStyleProps->LineStyle)
4345         && pBorderLine->GetColor() == Color(ColorTransparency, pStyleProps->Color)
4346         && pBorderLine->GetWidth() == o3tl::toTwips(pStyleProps->LineWidth, o3tl::Length::mm100))
4347     {
4348         return;
4349     }
4350 
4351     rtl::Reference<FastAttributeList> pAttr = FastSerializerHelper::createAttrList();
4352     pAttr->add( FSNS( XML_w, XML_val ), pVal );
4353 
4354     if ( pBorderLine && !pBorderLine->isEmpty() )
4355     {
4356         // Compute the sz attribute
4357 
4358         double const fConverted( ::editeng::ConvertBorderWidthToWord(
4359                 pBorderLine->GetBorderLineStyle(), pBorderLine->GetWidth()));
4360         // The unit is the 8th of point
4361         sal_Int32 nWidth = sal_Int32( fConverted / 2.5 );
4362         const sal_Int32 nMinWidth = 2;
4363         const sal_Int32 nMaxWidth = 96;
4364 
4365         if ( nWidth > nMaxWidth )
4366             nWidth = nMaxWidth;
4367         else if ( nWidth < nMinWidth )
4368             nWidth = nMinWidth;
4369 
4370         pAttr->add( FSNS( XML_w, XML_sz ), OString::number( nWidth ) );
4371 
4372         // Get the distance (in pt)
4373         pAttr->add(FSNS(XML_w, XML_space), OString::number(rtl::math::round(nDist / 20.0)));
4374 
4375         // Get the color code as an RRGGBB hex value
4376         OString sColor( msfilter::util::ConvertColor( pBorderLine->GetColor( ) ) );
4377         pAttr->add( FSNS(XML_w, XML_color), sColor);
4378 
4379         model::ComplexColor const& rComplexColor = pBorderLine->getComplexColor();
4380         lclAddThemeColorAttributes(pAttr, rComplexColor);
4381     }
4382 
4383     if (bWriteShadow)
4384     {
4385         // Set the shadow value
4386         pAttr->add( FSNS( XML_w, XML_shadow ), "1" );
4387     }
4388 
4389     pSerializer->singleElementNS( XML_w, elementToken, pAttr );
4390 }
4391 
lcl_getTableCellBorderOptions(bool bEcma)4392 static OutputBorderOptions lcl_getTableCellBorderOptions(bool bEcma)
4393 {
4394     OutputBorderOptions rOptions;
4395 
4396     rOptions.tag = XML_tcBorders;
4397     rOptions.bUseStartEnd = !bEcma;
4398     rOptions.bWriteTag = true;
4399     rOptions.bWriteDistance = false;
4400 
4401     return rOptions;
4402 }
4403 
lcl_getBoxBorderOptions()4404 static OutputBorderOptions lcl_getBoxBorderOptions()
4405 {
4406     OutputBorderOptions rOptions;
4407 
4408     rOptions.tag = XML_pBdr;
4409     rOptions.bUseStartEnd = false;
4410     rOptions.bWriteTag = false;
4411     rOptions.bWriteDistance = true;
4412 
4413     return rOptions;
4414 }
4415 
impl_borders(FSHelperPtr const & pSerializer,const SvxBoxItem & rBox,const OutputBorderOptions & rOptions,std::map<SvxBoxItemLine,css::table::BorderLine2> & rTableStyleConf,ww8::Frame * pFramePr=nullptr)4416 static void impl_borders( FSHelperPtr const & pSerializer,
4417                           const SvxBoxItem& rBox,
4418                           const OutputBorderOptions& rOptions,
4419                           std::map<SvxBoxItemLine,
4420                           css::table::BorderLine2> &rTableStyleConf,
4421                           ww8::Frame* pFramePr = nullptr)
4422 {
4423     static const SvxBoxItemLine aBorders[] =
4424     {
4425         SvxBoxItemLine::TOP, SvxBoxItemLine::LEFT, SvxBoxItemLine::BOTTOM, SvxBoxItemLine::RIGHT
4426     };
4427 
4428     const sal_Int32 aXmlElements[] =
4429     {
4430         XML_top,
4431         rOptions.bUseStartEnd ? XML_start : XML_left,
4432         XML_bottom,
4433         rOptions.bUseStartEnd ? XML_end : XML_right
4434     };
4435     bool tagWritten = false;
4436     const SvxBoxItemLine* pBrd = aBorders;
4437 
4438     for( int i = 0; i < 4; ++i, ++pBrd )
4439     {
4440         const SvxBorderLine* pLn = rBox.GetLine( *pBrd );
4441         const table::BorderLine2 *aStyleProps = nullptr;
4442         auto it = rTableStyleConf.find( *pBrd );
4443         if( it != rTableStyleConf.end() )
4444             aStyleProps = &(it->second);
4445 
4446         if (!tagWritten && rOptions.bWriteTag)
4447         {
4448             pSerializer->startElementNS(XML_w, rOptions.tag);
4449             tagWritten = true;
4450         }
4451 
4452         bool bWriteShadow = false;
4453         if (rOptions.aShadowLocation == SvxShadowLocation::NONE)
4454         {
4455             // The border has no shadow
4456         }
4457         else if (rOptions.aShadowLocation == SvxShadowLocation::BottomRight)
4458         {
4459             // Special case of 'Bottom-Right' shadow:
4460             // If the shadow location is 'Bottom-Right' - then turn on the shadow
4461             // for ALL the sides. This is because in Word - if you select a shadow
4462             // for a border - it turn on the shadow for ALL the sides (but shows only
4463             // the bottom-right one).
4464             // This is so that no information will be lost if passed through LibreOffice
4465             bWriteShadow = true;
4466         }
4467         else
4468         {
4469             // If there is a shadow, and it's not the regular 'Bottom-Right',
4470             // then write only the 'shadowed' sides of the border
4471             if  (
4472                     ((rOptions.aShadowLocation == SvxShadowLocation::TopLeft    || rOptions.aShadowLocation == SvxShadowLocation::TopRight  ) && *pBrd == SvxBoxItemLine::TOP   ) ||
4473                     ((rOptions.aShadowLocation == SvxShadowLocation::TopLeft    || rOptions.aShadowLocation == SvxShadowLocation::BottomLeft) && *pBrd == SvxBoxItemLine::LEFT  ) ||
4474                     ((rOptions.aShadowLocation == SvxShadowLocation::BottomLeft                                                             ) && *pBrd == SvxBoxItemLine::BOTTOM) ||
4475                     ((rOptions.aShadowLocation == SvxShadowLocation::TopRight                                                               ) && *pBrd == SvxBoxItemLine::RIGHT )
4476                 )
4477             {
4478                 bWriteShadow = true;
4479             }
4480         }
4481 
4482         sal_uInt16 nDist = 0;
4483         if (rOptions.bWriteDistance)
4484         {
4485             if (rOptions.pDistances)
4486             {
4487                 if ( *pBrd == SvxBoxItemLine::TOP)
4488                     nDist = rOptions.pDistances->nTop;
4489                 else if ( *pBrd == SvxBoxItemLine::LEFT)
4490                     nDist = rOptions.pDistances->nLeft;
4491                 else if ( *pBrd == SvxBoxItemLine::BOTTOM)
4492                     nDist = rOptions.pDistances->nBottom;
4493                 else if ( *pBrd == SvxBoxItemLine::RIGHT)
4494                     nDist = rOptions.pDistances->nRight;
4495             }
4496             else
4497             {
4498                 nDist = rBox.GetDistance(*pBrd);
4499             }
4500         }
4501 
4502         if (pFramePr)
4503         {
4504             assert(rOptions.bWriteDistance && !rOptions.pDistances);
4505 
4506             // In addition to direct properties, and paragraph styles,
4507             // for framePr-floated paragraphs the frame borders also affect the exported values.
4508 
4509             // For border spacing, there is a special situation to consider
4510             // because a compat setting ignores left/right paragraph spacing on layout.
4511             const SwFrameFormat& rFormat = pFramePr->GetFrameFormat();
4512             const SvxBoxItem& rFramePrBox = rFormat.GetBox();
4513             const IDocumentSettingAccess& rIDSA = rFormat.GetDoc()->getIDocumentSettingAccess();
4514             if (rIDSA.get(DocumentSettingId::INVERT_BORDER_SPACING)
4515                 && (*pBrd == SvxBoxItemLine::LEFT || *pBrd == SvxBoxItemLine::RIGHT))
4516             {
4517                 // only the frame's border spacing affects layout - so use that value instead.
4518                 nDist = rFramePrBox.GetDistance(*pBrd);
4519             }
4520             else
4521             {
4522                 nDist += rFramePrBox.GetDistance(*pBrd);
4523             }
4524 
4525             // Unless the user added a paragraph border, the border normally comes from the frame.
4526             if (!pLn)
4527                 pLn = rFramePrBox.GetLine(*pBrd);
4528         }
4529 
4530         impl_borderLine( pSerializer, aXmlElements[i], pLn, nDist, bWriteShadow, aStyleProps );
4531     }
4532     if (tagWritten && rOptions.bWriteTag) {
4533         pSerializer->endElementNS( XML_w, rOptions.tag );
4534     }
4535 }
4536 
ImplCellMargins(FSHelperPtr const & pSerializer,const SvxBoxItem & rBox,sal_Int32 tag,bool bUseStartEnd,const SvxBoxItem * pDefaultMargins)4537 void DocxAttributeOutput::ImplCellMargins( FSHelperPtr const & pSerializer, const SvxBoxItem& rBox, sal_Int32 tag, bool bUseStartEnd, const SvxBoxItem* pDefaultMargins)
4538 {
4539     static const SvxBoxItemLine aBorders[] =
4540     {
4541         SvxBoxItemLine::TOP, SvxBoxItemLine::LEFT, SvxBoxItemLine::BOTTOM, SvxBoxItemLine::RIGHT
4542     };
4543 
4544     const sal_Int32 aXmlElements[] =
4545     {
4546         XML_top,
4547         bUseStartEnd ? XML_start : XML_left,
4548         XML_bottom,
4549         bUseStartEnd ? XML_end : XML_right
4550     };
4551     bool tagWritten = false;
4552     const SvxBoxItemLine* pBrd = aBorders;
4553     for( int i = 0; i < 4; ++i, ++pBrd )
4554     {
4555         sal_Int32 nDist = sal_Int32( rBox.GetDistance( *pBrd ) );
4556 
4557         if (pDefaultMargins)
4558         {
4559             // Skip output if cell margin == table default margin
4560             if (sal_Int32( pDefaultMargins->GetDistance( *pBrd ) ) == nDist)
4561                 continue;
4562         }
4563 
4564         if (!tagWritten) {
4565             pSerializer->startElementNS(XML_w, tag);
4566             tagWritten = true;
4567         }
4568         pSerializer->singleElementNS( XML_w, aXmlElements[i],
4569                FSNS( XML_w, XML_w ), OString::number(nDist),
4570                FSNS( XML_w, XML_type ), "dxa" );
4571     }
4572     if (tagWritten) {
4573         pSerializer->endElementNS( XML_w, tag );
4574     }
4575 }
4576 
TableCellProperties(ww8::WW8TableNodeInfoInner::Pointer_t const & pTableTextNodeInfoInner,sal_uInt32 nCell,sal_uInt32 nRow)4577 void DocxAttributeOutput::TableCellProperties( ww8::WW8TableNodeInfoInner::Pointer_t const & pTableTextNodeInfoInner, sal_uInt32 nCell, sal_uInt32 nRow )
4578 {
4579     m_pSerializer->startElementNS(XML_w, XML_tcPr);
4580 
4581     const SwTableBox *pTableBox = pTableTextNodeInfoInner->getTableBox( );
4582 
4583     bool const bEcma = GetExport().GetFilter().getVersion() == oox::core::ECMA_376_1ST_EDITION;
4584 
4585     // Output any table cell redlines if there are any attached to this specific cell
4586     TableCellRedline( pTableTextNodeInfoInner );
4587 
4588     // Cell preferred width
4589     SwTwips nWidth = GetGridCols( pTableTextNodeInfoInner )->at( nCell );
4590     if ( nCell )
4591         nWidth = nWidth - GetGridCols( pTableTextNodeInfoInner )->at( nCell - 1 );
4592     m_pSerializer->singleElementNS( XML_w, XML_tcW,
4593            FSNS( XML_w, XML_w ), OString::number(nWidth),
4594            FSNS( XML_w, XML_type ), "dxa" );
4595 
4596     // Horizontal spans
4597     const SwWriteTableRows& rRows = m_xTableWrt->GetRows( );
4598     if (nRow >= rRows.size())
4599         SAL_WARN("sw.ww8", "DocxAttributeOutput::TableCellProperties: out of range row: " << nRow);
4600     else
4601     {
4602         SwWriteTableRow *pRow = rRows[ nRow ].get();
4603         const SwWriteTableCells& rTableCells =  pRow->GetCells();
4604         if (nCell < rTableCells.size() )
4605         {
4606             const SwWriteTableCell& rCell = *rTableCells[nCell];
4607             const sal_uInt16 nColSpan = rCell.GetColSpan();
4608             if ( nColSpan > 1 )
4609                 m_pSerializer->singleElementNS( XML_w, XML_gridSpan,
4610                         FSNS( XML_w, XML_val ), OString::number(nColSpan) );
4611         }
4612     }
4613 
4614     // Vertical merges
4615     ww8::RowSpansPtr xRowSpans = pTableTextNodeInfoInner->getRowSpansOfRow();
4616     sal_Int32 vSpan = (*xRowSpans)[nCell];
4617     if ( vSpan > 1 )
4618     {
4619         m_pSerializer->singleElementNS(XML_w, XML_vMerge, FSNS(XML_w, XML_val), "restart");
4620     }
4621     else if ( vSpan < 0 )
4622     {
4623         m_pSerializer->singleElementNS(XML_w, XML_vMerge, FSNS(XML_w, XML_val), "continue");
4624     }
4625 
4626     if (const SfxGrabBagItem* pItem = pTableBox->GetFrameFormat()->GetAttrSet().GetItem<SfxGrabBagItem>(RES_FRMATR_GRABBAG))
4627     {
4628         const std::map<OUString, uno::Any>& rGrabBag = pItem->GetGrabBag();
4629         std::map<OUString, uno::Any>::const_iterator it = rGrabBag.find(u"CellCnfStyle"_ustr);
4630         if (it != rGrabBag.end())
4631         {
4632             uno::Sequence<beans::PropertyValue> aAttributes = it->second.get< uno::Sequence<beans::PropertyValue> >();
4633             m_pTableStyleExport->CnfStyle(aAttributes);
4634         }
4635     }
4636 
4637 
4638     const SvxBoxItem& rBox = pTableBox->GetFrameFormat( )->GetBox( );
4639     const SvxBoxItem& rDefaultBox = (*m_TableFirstCells.rbegin())->getTableBox( )->GetFrameFormat( )->GetBox( );
4640     {
4641         // The cell borders
4642         impl_borders(m_pSerializer, rBox, lcl_getTableCellBorderOptions(bEcma),
4643                      m_aTableStyleConfs.back());
4644     }
4645 
4646     TableBackgrounds( pTableTextNodeInfoInner );
4647 
4648     {
4649         // Cell margins
4650         DocxAttributeOutput::ImplCellMargins( m_pSerializer, rBox, XML_tcMar, !bEcma, &rDefaultBox );
4651     }
4652 
4653     TableVerticalCell( pTableTextNodeInfoInner );
4654 
4655     m_pSerializer->endElementNS( XML_w, XML_tcPr );
4656 }
4657 
InitTableHelper(ww8::WW8TableNodeInfoInner::Pointer_t const & pTableTextNodeInfoInner)4658 void DocxAttributeOutput::InitTableHelper( ww8::WW8TableNodeInfoInner::Pointer_t const & pTableTextNodeInfoInner )
4659 {
4660     const SwTable* pTable = pTableTextNodeInfoInner->getTable();
4661     if (m_xTableWrt && pTable == m_xTableWrt->GetTable())
4662         return;
4663 
4664     tools::Long nPageSize = 0;
4665     bool bRelBoxSize = false;
4666 
4667     // Create the SwWriteTable instance to use col spans (and maybe other infos)
4668     GetTablePageSize( pTableTextNodeInfoInner.get(), nPageSize, bRelBoxSize );
4669 
4670     const SwFrameFormat *pFormat = pTable->GetFrameFormat( );
4671     const sal_uInt32 nTableSz = static_cast<sal_uInt32>(pFormat->GetFrameSize( ).GetWidth( ));
4672 
4673     const SwHTMLTableLayout *pLayout = pTable->GetHTMLTableLayout();
4674     if( pLayout && pLayout->IsExportable() )
4675         m_xTableWrt.reset(new SwWriteTable(pTable, pLayout));
4676     else
4677         m_xTableWrt.reset(new SwWriteTable(pTable, pTable->GetTabLines(), nPageSize, nTableSz, false));
4678 }
4679 
StartTable(ww8::WW8TableNodeInfoInner::Pointer_t const & pTableTextNodeInfoInner)4680 void DocxAttributeOutput::StartTable( ww8::WW8TableNodeInfoInner::Pointer_t const & pTableTextNodeInfoInner )
4681 {
4682     m_aTableStyleConfs.emplace_back();
4683 
4684     // In case any paragraph SDT's are open, close them here.
4685     EndParaSdtBlock();
4686 
4687     m_pSerializer->startElementNS(XML_w, XML_tbl);
4688 
4689     m_TableFirstCells.push_back(pTableTextNodeInfoInner);
4690     m_LastOpenCell.push_back(-1);
4691     m_LastClosedCell.push_back(-1);
4692 
4693     InitTableHelper( pTableTextNodeInfoInner );
4694     TableDefinition( pTableTextNodeInfoInner );
4695 }
4696 
EndTable()4697 void DocxAttributeOutput::EndTable()
4698 {
4699     m_pSerializer->endElementNS( XML_w, XML_tbl );
4700 
4701     if ( m_tableReference.m_nTableDepth > 0 )
4702         --m_tableReference.m_nTableDepth;
4703 
4704     m_LastClosedCell.pop_back();
4705     m_LastOpenCell.pop_back();
4706     m_TableFirstCells.pop_back();
4707 
4708     // We closed the table; if it is a nested table, the cell that contains it
4709     // still continues
4710     // set to true only if we were in a nested table, not otherwise.
4711     if( !m_TableFirstCells.empty() )
4712         m_tableReference.m_bTableCellOpen = true;
4713 
4714     // Cleans the table helper
4715     m_xTableWrt.reset();
4716 
4717     m_aTableStyleConfs.pop_back();
4718 }
4719 
StartTableRow(ww8::WW8TableNodeInfoInner::Pointer_t const & pTableTextNodeInfoInner)4720 void DocxAttributeOutput::StartTableRow( ww8::WW8TableNodeInfoInner::Pointer_t const & pTableTextNodeInfoInner )
4721 {
4722     m_pSerializer->startElementNS(XML_w, XML_tr);
4723 
4724     // Output the row properties
4725     m_pSerializer->startElementNS(XML_w, XML_trPr);
4726 
4727     // Header row: tblHeader
4728     const SwTable *pTable = pTableTextNodeInfoInner->getTable( );
4729     if ( pTable->GetRowsToRepeat( ) > pTableTextNodeInfoInner->getRow( ) )
4730         m_pSerializer->singleElementNS(XML_w, XML_tblHeader, FSNS(XML_w, XML_val), "true"); // TODO to overwrite table style may need explicit false
4731 
4732     TableRowRedline( pTableTextNodeInfoInner );
4733     TableHeight( pTableTextNodeInfoInner );
4734     TableCanSplit( pTableTextNodeInfoInner );
4735 
4736     const SwTableBox *pTableBox = pTableTextNodeInfoInner->getTableBox();
4737     const SwTableLine* pTableLine = pTableBox->GetUpper();
4738     if (const SfxGrabBagItem* pItem = pTableLine->GetFrameFormat()->GetAttrSet().GetItem<SfxGrabBagItem>(RES_FRMATR_GRABBAG))
4739     {
4740         const std::map<OUString, uno::Any>& rGrabBag = pItem->GetGrabBag();
4741         std::map<OUString, uno::Any>::const_iterator it = rGrabBag.find(u"RowCnfStyle"_ustr);
4742         if (it != rGrabBag.end())
4743         {
4744             uno::Sequence<beans::PropertyValue> aAttributes = it->second.get< uno::Sequence<beans::PropertyValue> >();
4745             m_pTableStyleExport->CnfStyle(aAttributes);
4746         }
4747     }
4748 
4749 
4750     m_pSerializer->endElementNS( XML_w, XML_trPr );
4751 }
4752 
EndTableRow()4753 void DocxAttributeOutput::EndTableRow( )
4754 {
4755     m_pSerializer->endElementNS( XML_w, XML_tr );
4756     m_LastOpenCell.back() = -1;
4757     m_LastClosedCell.back() = -1;
4758 }
4759 
StartTableCell(ww8::WW8TableNodeInfoInner::Pointer_t const & pTableTextNodeInfoInner,sal_uInt32 nCell,sal_uInt32 nRow)4760 void DocxAttributeOutput::StartTableCell( ww8::WW8TableNodeInfoInner::Pointer_t const & pTableTextNodeInfoInner, sal_uInt32 nCell, sal_uInt32 nRow )
4761 {
4762     m_LastOpenCell.back() = nCell;
4763 
4764     InitTableHelper( pTableTextNodeInfoInner );
4765 
4766     // check tracked table column deletion or insertion
4767     const SwTableBox* pTabBox = pTableTextNodeInfoInner->getTableBox();
4768     SwRedlineTable::size_type nChange = pTabBox->GetRedline();
4769     if (nChange != SwRedlineTable::npos)
4770         m_tableReference.m_bTableCellChanged = true;
4771 
4772     m_pSerializer->startElementNS(XML_w, XML_tc);
4773 
4774     // Write the cell properties here
4775     TableCellProperties( pTableTextNodeInfoInner, nCell, nRow );
4776 
4777     m_tableReference.m_bTableCellOpen = true;
4778 }
4779 
EndTableCell(sal_uInt32 nCell)4780 void DocxAttributeOutput::EndTableCell(sal_uInt32 nCell)
4781 {
4782     m_LastClosedCell.back() = nCell;
4783     m_LastOpenCell.back() = -1;
4784 
4785     if (m_tableReference.m_bTableCellParaSdtOpen)
4786         EndParaSdtBlock();
4787 
4788     m_pSerializer->endElementNS( XML_w, XML_tc );
4789 
4790     m_tableReference.m_bTableCellOpen = false;
4791     m_tableReference.m_bTableCellParaSdtOpen = false;
4792     m_tableReference.m_bTableCellChanged = false;
4793 }
4794 
StartStyles()4795 void DocxAttributeOutput::StartStyles()
4796 {
4797     m_pSerializer->startElementNS( XML_w, XML_styles,
4798             FSNS( XML_xmlns, XML_w ),   GetExport().GetFilter().getNamespaceURL(OOX_NS(doc)),
4799             FSNS( XML_xmlns, XML_w14 ), GetExport().GetFilter().getNamespaceURL(OOX_NS(w14)),
4800             FSNS( XML_xmlns, XML_mc ),  GetExport().GetFilter().getNamespaceURL(OOX_NS(mce)),
4801             FSNS( XML_mc, XML_Ignorable ), "w14" );
4802 
4803     DocDefaults();
4804     LatentStyles();
4805 }
4806 
DocxStringGetToken(DocxStringTokenMap const * pMap,std::u16string_view rName)4807 sal_Int32 DocxStringGetToken(DocxStringTokenMap const * pMap, std::u16string_view rName)
4808 {
4809     OString sName = OUStringToOString(rName, RTL_TEXTENCODING_UTF8);
4810     while (pMap->pToken)
4811     {
4812         if (sName == pMap->pToken)
4813             return pMap->nToken;
4814         ++pMap;
4815     }
4816     return 0;
4817 }
4818 
4819 namespace
4820 {
4821 
4822 DocxStringTokenMap const aDefaultTokens[] = {
4823     {"defQFormat", XML_defQFormat},
4824     {"defUnhideWhenUsed", XML_defUnhideWhenUsed},
4825     {"defSemiHidden", XML_defSemiHidden},
4826     {"count", XML_count},
4827     {"defUIPriority", XML_defUIPriority},
4828     {"defLockedState", XML_defLockedState},
4829     {nullptr, 0}
4830 };
4831 
4832 DocxStringTokenMap const aExceptionTokens[] = {
4833     {"name", XML_name},
4834     {"locked", XML_locked},
4835     {"uiPriority", XML_uiPriority},
4836     {"semiHidden", XML_semiHidden},
4837     {"unhideWhenUsed", XML_unhideWhenUsed},
4838     {"qFormat", XML_qFormat},
4839     {nullptr, 0}
4840 };
4841 
4842 }
4843 
LatentStyles()4844 void DocxAttributeOutput::LatentStyles()
4845 {
4846     // Do we have latent styles available?
4847     uno::Reference<beans::XPropertySet> xPropertySet(m_rExport.m_rDoc.GetDocShell()->GetBaseModel(), uno::UNO_QUERY_THROW);
4848     uno::Sequence<beans::PropertyValue> aInteropGrabBag;
4849     xPropertySet->getPropertyValue(u"InteropGrabBag"_ustr) >>= aInteropGrabBag;
4850     uno::Sequence<beans::PropertyValue> aLatentStyles;
4851     auto pProp = std::find_if(std::cbegin(aInteropGrabBag), std::cend(aInteropGrabBag),
4852         [](const beans::PropertyValue& rProp) { return rProp.Name == "latentStyles"; });
4853     if (pProp != std::cend(aInteropGrabBag))
4854         pProp->Value >>= aLatentStyles;
4855     if (!aLatentStyles.hasElements())
4856         return;
4857 
4858     // Extract default attributes first.
4859     rtl::Reference<sax_fastparser::FastAttributeList> pAttributeList = FastSerializerHelper::createAttrList();
4860     uno::Sequence<beans::PropertyValue> aLsdExceptions;
4861     for (const auto& rLatentStyle : aLatentStyles)
4862     {
4863         if (sal_Int32 nToken = DocxStringGetToken(aDefaultTokens, rLatentStyle.Name))
4864             pAttributeList->add(FSNS(XML_w, nToken), rLatentStyle.Value.get<OUString>());
4865         else if (rLatentStyle.Name == "lsdExceptions")
4866             rLatentStyle.Value >>= aLsdExceptions;
4867     }
4868 
4869     m_pSerializer->startElementNS(XML_w, XML_latentStyles, detachFrom(pAttributeList));
4870 
4871     // Then handle the exceptions.
4872     for (const auto& rLsdException : aLsdExceptions)
4873     {
4874         pAttributeList = FastSerializerHelper::createAttrList();
4875 
4876         uno::Sequence<beans::PropertyValue> aAttributes;
4877         rLsdException.Value >>= aAttributes;
4878         for (const auto& rAttribute : aAttributes)
4879             if (sal_Int32 nToken = DocxStringGetToken(aExceptionTokens, rAttribute.Name))
4880                 pAttributeList->add(FSNS(XML_w, nToken), rAttribute.Value.get<OUString>());
4881 
4882         m_pSerializer->singleElementNS(XML_w, XML_lsdException, detachFrom(pAttributeList));
4883     }
4884 
4885     m_pSerializer->endElementNS(XML_w, XML_latentStyles);
4886 }
4887 
OutputDefaultItem(const SfxPoolItem & rHt)4888 void DocxAttributeOutput::OutputDefaultItem(const SfxPoolItem& rHt)
4889 {
4890     bool bMustWrite = true;
4891     switch (rHt.Which())
4892     {
4893         case RES_CHRATR_CASEMAP:
4894             bMustWrite = static_cast< const SvxCaseMapItem& >(rHt).GetCaseMap() != SvxCaseMap::NotMapped;
4895             break;
4896         case RES_CHRATR_COLOR:
4897             bMustWrite = static_cast< const SvxColorItem& >(rHt).GetValue() != COL_AUTO;
4898             break;
4899         case RES_CHRATR_CONTOUR:
4900             bMustWrite = static_cast< const SvxContourItem& >(rHt).GetValue();
4901             break;
4902         case RES_CHRATR_CROSSEDOUT:
4903             bMustWrite = static_cast< const SvxCrossedOutItem& >(rHt).GetStrikeout() != STRIKEOUT_NONE;
4904             break;
4905         case RES_CHRATR_ESCAPEMENT:
4906             bMustWrite = static_cast< const SvxEscapementItem& >(rHt).GetEscapement() != SvxEscapement::Off;
4907             break;
4908         case RES_CHRATR_FONT:
4909             bMustWrite = true;
4910             break;
4911         case RES_CHRATR_FONTSIZE:
4912             bMustWrite = static_cast< const SvxFontHeightItem& >(rHt).GetHeight() != 200; // see StyleSheetTable_Impl::StyleSheetTable_Impl() where we set this default
4913             break;
4914         case RES_CHRATR_KERNING:
4915             bMustWrite = static_cast< const SvxKerningItem& >(rHt).GetValue() != 0;
4916             break;
4917         case RES_CHRATR_LANGUAGE:
4918             bMustWrite = true;
4919             break;
4920         case RES_CHRATR_POSTURE:
4921             bMustWrite = static_cast< const SvxPostureItem& >(rHt).GetPosture() != ITALIC_NONE;
4922             break;
4923         case RES_CHRATR_SHADOWED:
4924             bMustWrite = static_cast< const SvxShadowedItem& >(rHt).GetValue();
4925             break;
4926         case RES_CHRATR_UNDERLINE:
4927             bMustWrite = static_cast< const SvxUnderlineItem& >(rHt).GetLineStyle() != LINESTYLE_NONE;
4928             break;
4929         case RES_CHRATR_WEIGHT:
4930             bMustWrite = static_cast< const SvxWeightItem& >(rHt).GetWeight() != WEIGHT_NORMAL;
4931             break;
4932         case RES_CHRATR_AUTOKERN:
4933             bMustWrite = static_cast< const SvxAutoKernItem& >(rHt).GetValue();
4934             break;
4935         case RES_CHRATR_BLINK:
4936             bMustWrite = static_cast< const SvxBlinkItem& >(rHt).GetValue();
4937             break;
4938         case RES_CHRATR_BACKGROUND:
4939             {
4940                 const SvxBrushItem& rBrushItem = static_cast< const SvxBrushItem& >(rHt);
4941                 bMustWrite = (rBrushItem.GetColor() != COL_AUTO ||
4942                               rBrushItem.GetShadingValue() != ShadingPattern::CLEAR ||
4943                               rBrushItem.GetGraphicObject() != nullptr);
4944             }
4945             break;
4946 
4947         case RES_CHRATR_CJK_FONT:
4948             bMustWrite = true;
4949             break;
4950         case RES_CHRATR_CJK_FONTSIZE:
4951             bMustWrite = false; // we have written it already as RES_CHRATR_FONTSIZE
4952             break;
4953         case RES_CHRATR_CJK_LANGUAGE:
4954             bMustWrite = true;
4955             break;
4956         case RES_CHRATR_CJK_POSTURE:
4957             bMustWrite = false; // we have written it already as RES_CHRATR_POSTURE
4958             break;
4959         case RES_CHRATR_CJK_WEIGHT:
4960             bMustWrite = false; // we have written it already as RES_CHRATR_WEIGHT
4961             break;
4962 
4963         case RES_CHRATR_CTL_FONT:
4964             bMustWrite = true;
4965             break;
4966         case RES_CHRATR_CTL_FONTSIZE:
4967             bMustWrite = static_cast< const SvxFontHeightItem& >(rHt).GetHeight() != 200; // see StyleSheetTable_Impl::StyleSheetTable_Impl() where we set this default
4968             break;
4969         case RES_CHRATR_CTL_LANGUAGE:
4970             bMustWrite = true;
4971             break;
4972         case RES_CHRATR_CTL_POSTURE:
4973             bMustWrite = static_cast< const SvxPostureItem& >(rHt).GetPosture() != ITALIC_NONE;
4974             break;
4975         case RES_CHRATR_CTL_WEIGHT:
4976             bMustWrite = static_cast< const SvxWeightItem& >(rHt).GetWeight() != WEIGHT_NORMAL;
4977             break;
4978 
4979         case RES_CHRATR_ROTATE:
4980             bMustWrite = static_cast< const SvxCharRotateItem& >(rHt).GetValue() != 0_deg10;
4981             break;
4982         case RES_CHRATR_EMPHASIS_MARK:
4983             bMustWrite = static_cast< const SvxEmphasisMarkItem& >(rHt).GetEmphasisMark() != FontEmphasisMark::NONE;
4984             break;
4985         case RES_CHRATR_TWO_LINES:
4986             bMustWrite = static_cast< const SvxTwoLinesItem& >(rHt).GetValue();
4987             break;
4988         case RES_CHRATR_SCALEW:
4989             bMustWrite = static_cast< const SvxCharScaleWidthItem& >(rHt).GetValue() != 100;
4990             break;
4991         case RES_CHRATR_RELIEF:
4992             bMustWrite = static_cast< const SvxCharReliefItem& >(rHt).GetValue() != FontRelief::NONE;
4993             break;
4994         case RES_CHRATR_HIDDEN:
4995             bMustWrite = static_cast< const SvxCharHiddenItem& >(rHt).GetValue();
4996             break;
4997         case RES_CHRATR_BOX:
4998             {
4999                 const SvxBoxItem& rBoxItem = static_cast< const SvxBoxItem& >(rHt);
5000                 bMustWrite = rBoxItem.GetTop() || rBoxItem.GetLeft() ||
5001                              rBoxItem.GetBottom() || rBoxItem.GetRight() ||
5002                              rBoxItem.GetSmallestDistance();
5003             }
5004             break;
5005         case RES_CHRATR_HIGHLIGHT:
5006             {
5007                 const SvxBrushItem& rBrushItem = static_cast< const SvxBrushItem& >(rHt);
5008                 bMustWrite = (rBrushItem.GetColor() != COL_AUTO ||
5009                               rBrushItem.GetShadingValue() != ShadingPattern::CLEAR ||
5010                               rBrushItem.GetGraphicObject() != nullptr);
5011             }
5012             break;
5013 
5014         case RES_PARATR_LINESPACING:
5015             bMustWrite = static_cast< const SvxLineSpacingItem& >(rHt).GetInterLineSpaceRule() != SvxInterLineSpaceRule::Off;
5016             break;
5017         case RES_PARATR_ADJUST:
5018             bMustWrite = static_cast< const SvxAdjustItem& >(rHt).GetAdjust() != SvxAdjust::Left;
5019             break;
5020         case RES_PARATR_SPLIT:
5021             bMustWrite = !static_cast< const SvxFormatSplitItem& >(rHt).GetValue();
5022             break;
5023         case RES_PARATR_WIDOWS:
5024             bMustWrite = static_cast< const SvxWidowsItem& >(rHt).GetValue();
5025             break;
5026         case RES_PARATR_TABSTOP:
5027             bMustWrite = static_cast< const SvxTabStopItem& >(rHt).Count() != 0;
5028             break;
5029         case RES_PARATR_HYPHENZONE:
5030             bMustWrite = true;
5031             break;
5032         case RES_PARATR_NUMRULE:
5033             bMustWrite = !static_cast< const SwNumRuleItem& >(rHt).GetValue().isEmpty();
5034             break;
5035         case RES_PARATR_SCRIPTSPACE:
5036             bMustWrite = !static_cast< const SfxBoolItem& >(rHt).GetValue();
5037             break;
5038         case RES_PARATR_HANGINGPUNCTUATION:
5039             bMustWrite = !static_cast< const SfxBoolItem& >(rHt).GetValue();
5040             break;
5041         case RES_PARATR_FORBIDDEN_RULES:
5042             bMustWrite = !static_cast< const SfxBoolItem& >(rHt).GetValue();
5043             break;
5044         case RES_PARATR_VERTALIGN:
5045             bMustWrite = static_cast< const SvxParaVertAlignItem& >(rHt).GetValue() != SvxParaVertAlignItem::Align::Automatic;
5046             break;
5047         case RES_PARATR_SNAPTOGRID:
5048             bMustWrite = !static_cast< const SvxParaGridItem& >(rHt).GetValue();
5049             break;
5050         case RES_CHRATR_GRABBAG:
5051             bMustWrite = true;
5052             break;
5053 
5054         default:
5055             SAL_INFO("sw.ww8", "Unhandled SfxPoolItem with id " << rHt.Which() );
5056             break;
5057     }
5058 
5059     if (bMustWrite)
5060         OutputItem(rHt);
5061 }
5062 
DocDefaults()5063 void DocxAttributeOutput::DocDefaults( )
5064 {
5065     // Write the '<w:docDefaults>' section here
5066     m_pSerializer->startElementNS(XML_w, XML_docDefaults);
5067 
5068     // Output the default run properties
5069     m_pSerializer->startElementNS(XML_w, XML_rPrDefault);
5070 
5071     StartStyleProperties(false, 0);
5072 
5073     for (int i = int(RES_CHRATR_BEGIN); i < int(RES_CHRATR_END); ++i)
5074         OutputDefaultItem(m_rExport.m_rDoc.GetDefault(i));
5075 
5076     EndStyleProperties(false);
5077 
5078     m_pSerializer->endElementNS(XML_w, XML_rPrDefault);
5079 
5080     // Output the default paragraph properties
5081     m_pSerializer->startElementNS(XML_w, XML_pPrDefault);
5082 
5083     StartStyleProperties(true, 0);
5084 
5085     for (int i = int(RES_PARATR_BEGIN); i < int(RES_PARATR_END); ++i)
5086         OutputDefaultItem(m_rExport.m_rDoc.GetDefault(i));
5087 
5088     EndStyleProperties(true);
5089 
5090     m_pSerializer->endElementNS(XML_w, XML_pPrDefault);
5091 
5092     m_pSerializer->endElementNS(XML_w, XML_docDefaults);
5093 }
5094 
EndStyles(sal_uInt16 nNumberOfStyles)5095 void DocxAttributeOutput::EndStyles( sal_uInt16 nNumberOfStyles )
5096 {
5097     // HACK
5098     // Ms Office seems to have an internal limitation of 4091 styles
5099     // and refuses to load .docx with more, even though the spec seems to allow that;
5100     // so simply if there are more styles, don't export those
5101     const sal_Int32 nCountStylesToWrite = MSWORD_MAX_STYLES_LIMIT - nNumberOfStyles;
5102     m_pTableStyleExport->TableStyles(nCountStylesToWrite);
5103     m_pSerializer->endElementNS( XML_w, XML_styles );
5104 }
5105 
DefaultStyle()5106 void DocxAttributeOutput::DefaultStyle()
5107 {
5108     // are these the values of enum ww::sti (see ../inc/wwstyles.hxx)?
5109     SAL_INFO("sw.ww8", "TODO DocxAttributeOutput::DefaultStyle()");
5110 }
5111 
5112 /* Writes <a:srcRect> tag back to document.xml if a file contains a cropped image.
5113 *  NOTE : Tested on images of type JPEG,EMF/WMF,BMP, PNG and GIF.
5114 */
WriteSrcRect(const css::uno::Reference<css::beans::XPropertySet> & xShapePropSet,const SwFrameFormat * pFrameFormat)5115 void DocxAttributeOutput::WriteSrcRect(
5116     const css::uno::Reference<css::beans::XPropertySet>& xShapePropSet,
5117     const SwFrameFormat* pFrameFormat)
5118 {
5119     uno::Reference<graphic::XGraphic> xGraphic;
5120     xShapePropSet->getPropertyValue(u"Graphic"_ustr) >>= xGraphic;
5121     const Graphic aGraphic(xGraphic);
5122 
5123     Size aOriginalSize(aGraphic.GetPrefSize());
5124 
5125     const MapMode aMap100mm( MapUnit::Map100thMM );
5126     const MapMode& rMapMode = aGraphic.GetPrefMapMode();
5127     if (rMapMode.GetMapUnit() == MapUnit::MapPixel)
5128     {
5129         aOriginalSize = Application::GetDefaultDevice()->PixelToLogic(aOriginalSize, aMap100mm);
5130     }
5131 
5132     css::text::GraphicCrop aGraphicCropStruct;
5133     xShapePropSet->getPropertyValue(u"GraphicCrop"_ustr) >>= aGraphicCropStruct;
5134     sal_Int32 nCropL = aGraphicCropStruct.Left;
5135     sal_Int32 nCropR = aGraphicCropStruct.Right;
5136     sal_Int32 nCropT = aGraphicCropStruct.Top;
5137     sal_Int32 nCropB = aGraphicCropStruct.Bottom;
5138 
5139     // simulate border padding as a negative crop.
5140     const SvxBoxItem* pBoxItem;
5141     if (pFrameFormat && (pBoxItem = pFrameFormat->GetItemIfSet(RES_BOX, false)))
5142     {
5143         nCropL -= pBoxItem->GetDistance( SvxBoxItemLine::LEFT );
5144         nCropR -= pBoxItem->GetDistance( SvxBoxItemLine::RIGHT );
5145         nCropT -= pBoxItem->GetDistance( SvxBoxItemLine::TOP );
5146         nCropB -= pBoxItem->GetDistance( SvxBoxItemLine::BOTTOM );
5147     }
5148 
5149     if (nCropL == 0 && nCropT == 0 && nCropR == 0 && nCropB == 0)
5150         return;
5151 
5152     double  widthMultiplier  = 100000.0/aOriginalSize.Width();
5153     double  heightMultiplier = 100000.0/aOriginalSize.Height();
5154 
5155     sal_Int32 left   = static_cast<sal_Int32>(rtl::math::round(nCropL * widthMultiplier));
5156     sal_Int32 right  = static_cast<sal_Int32>(rtl::math::round(nCropR * widthMultiplier));
5157     sal_Int32 top    = static_cast<sal_Int32>(rtl::math::round(nCropT * heightMultiplier));
5158     sal_Int32 bottom = static_cast<sal_Int32>(rtl::math::round(nCropB * heightMultiplier));
5159 
5160     m_pSerializer->singleElementNS( XML_a, XML_srcRect,
5161          XML_l, OString::number(left),
5162          XML_t, OString::number(top),
5163          XML_r, OString::number(right),
5164          XML_b, OString::number(bottom) );
5165 }
5166 
GetUnoTextFrame(css::uno::Reference<css::drawing::XShape> xShape)5167 uno::Reference<css::text::XTextFrame> DocxAttributeOutput::GetUnoTextFrame(
5168     css::uno::Reference<css::drawing::XShape> xShape)
5169 {
5170     return SwTextBoxHelper::getUnoTextFrame(xShape);
5171 }
5172 
CreateDocPrAttrList(DocxExport & rExport,int const nAnchorId,std::u16string_view const & rName,std::u16string_view const & rTitle,std::u16string_view const & rDescription)5173 static rtl::Reference<::sax_fastparser::FastAttributeList> CreateDocPrAttrList(
5174     DocxExport & rExport, int const nAnchorId, std::u16string_view const& rName,
5175     std::u16string_view const& rTitle, std::u16string_view const& rDescription)
5176 {
5177     rtl::Reference<::sax_fastparser::FastAttributeList> const pAttrs(FastSerializerHelper::createAttrList());
5178     pAttrs->add(XML_id, OString::number(nAnchorId));
5179     pAttrs->add(XML_name, rName);
5180     if (rExport.GetFilter().getVersion() != oox::core::ECMA_376_1ST_EDITION)
5181     {
5182         pAttrs->add(XML_descr, rDescription);
5183         pAttrs->add(XML_title, rTitle);
5184     }
5185     else
5186     {   // tdf#148952 no title attribute, merge it into descr
5187         OUString const value(rTitle.empty()
5188             ? OUString(rDescription)
5189             : rDescription.empty()
5190                 ? OUString(rTitle)
5191                 : OUString::Concat(rTitle) + "\n" + rDescription);
5192         pAttrs->add(XML_descr, value);
5193     }
5194     return pAttrs;
5195 }
5196 
FlyFrameGraphic(const SwGrfNode * pGrfNode,const Size & rSize,const SwFlyFrameFormat * pOLEFrameFormat,SwOLENode * pOLENode,const SdrObject * pSdrObj)5197 void DocxAttributeOutput::FlyFrameGraphic( const SwGrfNode* pGrfNode, const Size& rSize, const SwFlyFrameFormat* pOLEFrameFormat, SwOLENode* pOLENode, const SdrObject* pSdrObj )
5198 {
5199     SAL_INFO("sw.ww8", "TODO DocxAttributeOutput::FlyFrameGraphic( const SwGrfNode* pGrfNode, const Size& rSize, const SwFlyFrameFormat* pOLEFrameFormat, SwOLENode* pOLENode, const SdrObject* pSdrObj  ) - some stuff still missing" );
5200 
5201     GetSdtEndBefore(pSdrObj);
5202 
5203     // detect mis-use of the API
5204     assert(pGrfNode || (pOLEFrameFormat && pOLENode));
5205     const SwFrameFormat* pFrameFormat = pGrfNode ? pGrfNode->GetFlyFormat() : pOLEFrameFormat;
5206     // create the relation ID
5207     OString aRelId;
5208     OUString sSvgRelId;
5209     sal_Int32 nImageType;
5210     if ( pGrfNode && pGrfNode->IsLinkedFile() )
5211     {
5212         // linked image, just create the relation
5213         OUString aFileName;
5214         pGrfNode->GetFileFilterNms( &aFileName, nullptr );
5215 
5216         sal_Int32 const nFragment(aFileName.indexOf('#'));
5217         sal_Int32 const nForbiddenU(aFileName.indexOf("%5C"));
5218         sal_Int32 const nForbiddenL(aFileName.indexOf("%5c"));
5219         if (   (nForbiddenU != -1 && (nFragment == -1 || nForbiddenU < nFragment))
5220             || (nForbiddenL != -1 && (nFragment == -1 || nForbiddenL < nFragment)))
5221         {
5222             SAL_WARN("sw.ww8", "DocxAttributeOutput::FlyFrameGraphic: ignoring image with invalid link URL");
5223             return;
5224         }
5225 
5226         // TODO Convert the file name to relative for better interoperability
5227 
5228         aRelId = m_rExport.AddRelation(
5229                     oox::getRelationship(Relationship::IMAGE),
5230                     aFileName );
5231 
5232         nImageType = XML_link;
5233     }
5234     else
5235     {
5236         // inline, we also have to write the image itself
5237         Graphic aGraphic;
5238         if (pGrfNode)
5239             aGraphic = pGrfNode->GetGrf();
5240         else
5241             aGraphic = *pOLENode->GetGraphic();
5242 
5243         m_rDrawingML.SetFS(m_pSerializer); // to be sure that we write to the right stream
5244         auto pGraphicExport = m_rDrawingML.createGraphicExport();
5245         OUString aImageId = pGraphicExport->writeToStorage(aGraphic, false);
5246         aRelId = OUStringToOString(aImageId, RTL_TEXTENCODING_UTF8);
5247 
5248         if (aGraphic.getVectorGraphicData() && aGraphic.getVectorGraphicData()->getType() == VectorGraphicDataType::Svg)
5249         {
5250             sSvgRelId = pGraphicExport->writeToStorage(aGraphic, false, drawingml::GraphicExport::TypeHint::SVG);
5251         }
5252         nImageType = XML_embed;
5253     }
5254 
5255     // In case there are any grab-bag items on the graphic frame, emit them now.
5256     // These are always character grab-bags, as graphics are at-char or as-char in Word.
5257     if (const SfxGrabBagItem* pGrabBag = pFrameFormat->GetAttrSet().GetItemIfSet(RES_FRMATR_GRABBAG))
5258     {
5259         CharGrabBag(*pGrabBag);
5260     }
5261 
5262     rtl::Reference<sax_fastparser::FastAttributeList> xFrameAttributes(
5263         FastSerializerHelper::createAttrList());
5264     if (pGrfNode)
5265     {
5266         const SwAttrSet& rSet = pGrfNode->GetSwAttrSet();
5267         MirrorGraph eMirror = rSet.Get(RES_GRFATR_MIRRORGRF).GetValue();
5268         if (eMirror == MirrorGraph::Vertical || eMirror == MirrorGraph::Both)
5269             // Mirror on the vertical axis is a horizontal flip.
5270             xFrameAttributes->add(XML_flipH, "1");
5271         // RES_GRFATR_ROTATION is sal_uInt16; use sal_uInt32 for multiplication later
5272         if (Degree10 nRot = rSet.Get(RES_GRFATR_ROTATION).GetValue())
5273         {
5274             // RES_GRFATR_ROTATION is in 10ths of degree; convert to 100ths for macro
5275             sal_uInt32 mOOXMLRot = oox::drawingml::ExportRotateClockwisify(to<Degree100>(nRot));
5276             xFrameAttributes->add(XML_rot, OString::number(mOOXMLRot));
5277         }
5278     }
5279 
5280     css::uno::Reference<css::beans::XPropertySet> xShapePropSet;
5281     if (pSdrObj)
5282     {
5283         css::uno::Reference<css::drawing::XShape> xShape(
5284             const_cast<SdrObject*>(pSdrObj)->getUnoShape(), css::uno::UNO_QUERY);
5285         xShapePropSet.set(xShape, css::uno::UNO_QUERY);
5286         assert(xShapePropSet);
5287     }
5288 
5289     Size aSize = rSize;
5290     // We need the original (cropped, but unrotated) size of object. So prefer the object data,
5291     // and only use passed frame size as fallback.
5292     if (xShapePropSet)
5293     {
5294         if (css::awt::Size val; xShapePropSet->getPropertyValue(u"Size"_ustr) >>= val)
5295             aSize = Size(o3tl::toTwips(val.Width, o3tl::Length::mm100), o3tl::toTwips(val.Height, o3tl::Length::mm100));
5296     }
5297 
5298     m_rExport.SdrExporter().startDMLAnchorInline(pFrameFormat, aSize);
5299 
5300     // picture description (used for pic:cNvPr later too)
5301     OUString const descr(pGrfNode ? pGrfNode->GetDescription() : pOLEFrameFormat->GetObjDescription());
5302     OUString const title(pGrfNode ? pGrfNode->GetTitle() : pOLEFrameFormat->GetObjTitle());
5303     auto const docPrattrList(CreateDocPrAttrList(
5304         GetExport(), m_anchorId++, pFrameFormat->GetName(), title, descr));
5305     m_pSerializer->startElementNS( XML_wp, XML_docPr, docPrattrList );
5306 
5307     OUString sURL, sRelId;
5308     if (xShapePropSet)
5309     {
5310         xShapePropSet->getPropertyValue(u"HyperLinkURL"_ustr) >>= sURL;
5311         if(!sURL.isEmpty())
5312         {
5313             if (sURL.startsWith("#") && sURL.indexOf(' ') != -1 && !sURL.endsWith("|outline") && !sURL.endsWith("|table") &&
5314                 !sURL.endsWith("|frame") && !sURL.endsWith("|graphic") && !sURL.endsWith("|ole") && !sURL.endsWith("|region"))
5315             {
5316                 // Spaces are prohibited in bookmark name.
5317                 sURL = sURL.replace(' ', '_');
5318             }
5319             sRelId = GetExport().GetFilter().addRelation( m_pSerializer->getOutputStream(),
5320                         oox::getRelationship(Relationship::HYPERLINK),
5321                         sURL, !sURL.startsWith("#") );
5322             m_pSerializer->singleElementNS( XML_a, XML_hlinkClick,
5323                 FSNS( XML_xmlns, XML_a ), "http://schemas.openxmlformats.org/drawingml/2006/main",
5324                 FSNS( XML_r, XML_id ), sRelId);
5325         }
5326         AddExtLst(m_pSerializer, GetExport(), xShapePropSet);
5327     }
5328 
5329     m_pSerializer->endElementNS( XML_wp, XML_docPr );
5330 
5331     m_pSerializer->startElementNS(XML_wp, XML_cNvGraphicFramePr);
5332     // TODO change aspect?
5333     m_pSerializer->singleElementNS( XML_a, XML_graphicFrameLocks,
5334             FSNS( XML_xmlns, XML_a ), GetExport().GetFilter().getNamespaceURL(OOX_NS(dml)),
5335             XML_noChangeAspect, "1" );
5336     m_pSerializer->endElementNS( XML_wp, XML_cNvGraphicFramePr );
5337 
5338     m_pSerializer->startElementNS( XML_a, XML_graphic,
5339             FSNS( XML_xmlns, XML_a ), GetExport().GetFilter().getNamespaceURL(OOX_NS(dml)) );
5340     m_pSerializer->startElementNS( XML_a, XML_graphicData,
5341             XML_uri, "http://schemas.openxmlformats.org/drawingml/2006/picture" );
5342 
5343     m_pSerializer->startElementNS( XML_pic, XML_pic,
5344             FSNS( XML_xmlns, XML_pic ), GetExport().GetFilter().getNamespaceURL(OOX_NS(dmlPicture)) );
5345 
5346     m_pSerializer->startElementNS(XML_pic, XML_nvPicPr);
5347     // It seems pic:cNvpr and wp:docPr are pretty much the same thing with the same attributes
5348     m_pSerializer->startElementNS(XML_pic, XML_cNvPr, docPrattrList);
5349 
5350     if(!sURL.isEmpty())
5351         m_pSerializer->singleElementNS(XML_a, XML_hlinkClick, FSNS(XML_r, XML_id), sRelId);
5352 
5353     m_pSerializer->endElementNS( XML_pic, XML_cNvPr );
5354 
5355     m_pSerializer->startElementNS(XML_pic, XML_cNvPicPr);
5356     // TODO change aspect?
5357     m_pSerializer->singleElementNS( XML_a, XML_picLocks,
5358             XML_noChangeAspect, "1", XML_noChangeArrowheads, "1" );
5359     m_pSerializer->endElementNS( XML_pic, XML_cNvPicPr );
5360     m_pSerializer->endElementNS( XML_pic, XML_nvPicPr );
5361 
5362     // the actual picture
5363     m_pSerializer->startElementNS(XML_pic, XML_blipFill);
5364 
5365 /* At this point we are certain that, WriteImage returns empty RelId
5366    for unhandled graphic type. Therefore we write the picture description
5367    and not the relation( coz there ain't any), so that the user knows
5368    there is an image/graphic in the doc but it is broken instead of
5369    completely discarding it.
5370 */
5371     if ( aRelId.isEmpty() )
5372         m_pSerializer->startElementNS(XML_a, XML_blip);
5373     else
5374         m_pSerializer->startElementNS(XML_a, XML_blip, FSNS(XML_r, nImageType), aRelId);
5375 
5376     const SfxEnumItemInterface* pGrafModeItem = nullptr;
5377     if ( pGrfNode && (pGrafModeItem = pGrfNode->GetSwAttrSet().GetItemIfSet(RES_GRFATR_DRAWMODE)))
5378     {
5379         GraphicDrawMode nMode = static_cast<GraphicDrawMode>(pGrafModeItem->GetEnumValue());
5380         if (nMode == GraphicDrawMode::Greys)
5381             m_pSerializer->singleElementNS (XML_a, XML_grayscl);
5382         else if (nMode == GraphicDrawMode::Mono) //black/white has a 0,5 threshold in LibreOffice
5383             m_pSerializer->singleElementNS (XML_a, XML_biLevel, XML_thresh, OString::number(50000));
5384         else if (nMode == GraphicDrawMode::Watermark) //watermark has a brightness/luminance of 0,5 and contrast of -0.7 in LibreOffice
5385             m_pSerializer->singleElementNS( XML_a, XML_lum, XML_bright, OString::number(70000), XML_contrast, OString::number(-70000) );
5386     }
5387 
5388     if (!sSvgRelId.isEmpty())
5389     {
5390         auto pGraphicExport = m_rDrawingML.createGraphicExport();
5391         pGraphicExport->writeSvgExtension(sSvgRelId);
5392     }
5393 
5394     m_pSerializer->endElementNS( XML_a, XML_blip );
5395 
5396     if (xShapePropSet)
5397         WriteSrcRect(xShapePropSet, pFrameFormat);
5398 
5399     m_pSerializer->startElementNS(XML_a, XML_stretch);
5400     m_pSerializer->singleElementNS(XML_a, XML_fillRect);
5401     m_pSerializer->endElementNS( XML_a, XML_stretch );
5402     m_pSerializer->endElementNS( XML_pic, XML_blipFill );
5403 
5404     // TODO setup the right values below
5405     m_pSerializer->startElementNS(XML_pic, XML_spPr, XML_bwMode, "auto");
5406 
5407     m_pSerializer->startElementNS(XML_a, XML_xfrm, xFrameAttributes);
5408 
5409     m_pSerializer->singleElementNS(XML_a, XML_off, XML_x, "0", XML_y, "0");
5410     OString aWidth( OString::number( TwipsToEMU( aSize.Width() ) ) );
5411     OString aHeight( OString::number( TwipsToEMU( aSize.Height() ) ) );
5412     m_pSerializer->singleElementNS(XML_a, XML_ext, XML_cx, aWidth, XML_cy, aHeight);
5413     m_pSerializer->endElementNS( XML_a, XML_xfrm );
5414     m_pSerializer->startElementNS(XML_a, XML_prstGeom, XML_prst, "rect");
5415     m_pSerializer->singleElementNS(XML_a, XML_avLst);
5416     m_pSerializer->endElementNS( XML_a, XML_prstGeom );
5417 
5418     m_rDrawingML.SetFS(m_pSerializer); // to be sure that we write to the right stream
5419     if (xShapePropSet)
5420         m_rDrawingML.WriteFill(xShapePropSet, awt::Size(aSize.Width(), aSize.Height()));
5421 
5422     const SvxBoxItem& rBoxItem = pFrameFormat->GetBox();
5423     const SvxBorderLine* pLeft = rBoxItem.GetLine(SvxBoxItemLine::LEFT);
5424     const SvxBorderLine* pRight = rBoxItem.GetLine(SvxBoxItemLine::RIGHT);
5425     const SvxBorderLine* pTop = rBoxItem.GetLine(SvxBoxItemLine::TOP);
5426     const SvxBorderLine* pBottom = rBoxItem.GetLine(SvxBoxItemLine::BOTTOM);
5427     if (pLeft || pRight || pTop || pBottom)
5428         m_rExport.SdrExporter().writeBoxItemLine(rBoxItem);
5429 
5430     m_rExport.SdrExporter().writeDMLEffectLst(*pFrameFormat);
5431 
5432     m_pSerializer->endElementNS( XML_pic, XML_spPr );
5433 
5434     m_pSerializer->endElementNS( XML_pic, XML_pic );
5435 
5436     m_pSerializer->endElementNS( XML_a, XML_graphicData );
5437     m_pSerializer->endElementNS( XML_a, XML_graphic );
5438     m_rExport.SdrExporter().endDMLAnchorInline(pFrameFormat);
5439 }
5440 
WriteOLE2Obj(const SdrObject * pSdrObj,SwOLENode & rOLENode,const Size & rSize,const SwFlyFrameFormat * pFlyFrameFormat,const sal_Int8 nFormulaAlignment)5441 void DocxAttributeOutput::WriteOLE2Obj( const SdrObject* pSdrObj, SwOLENode& rOLENode, const Size& rSize, const SwFlyFrameFormat* pFlyFrameFormat, const sal_Int8 nFormulaAlignment )
5442 {
5443     if( WriteOLEChart( pSdrObj, rSize, pFlyFrameFormat ))
5444         return;
5445     if( WriteOLEMath( rOLENode , nFormulaAlignment))
5446         return;
5447     PostponeOLE( rOLENode, rSize, pFlyFrameFormat );
5448 }
5449 
WriteOLEChart(const SdrObject * pSdrObj,const Size & rSize,const SwFlyFrameFormat * pFlyFrameFormat)5450 bool DocxAttributeOutput::WriteOLEChart( const SdrObject* pSdrObj, const Size& rSize, const SwFlyFrameFormat* pFlyFrameFormat )
5451 {
5452     uno::Reference< drawing::XShape > xShape( const_cast<SdrObject*>(pSdrObj)->getUnoShape(), uno::UNO_QUERY );
5453     if (!xShape.is())
5454         return false;
5455 
5456     uno::Reference<beans::XPropertySet> const xPropSet(xShape, uno::UNO_QUERY);
5457     if (!xPropSet.is())
5458         return false;
5459 
5460     OUString clsid; // why is the property of type string, not sequence<byte>?
5461     xPropSet->getPropertyValue(u"CLSID"_ustr) >>= clsid;
5462     assert(!clsid.isEmpty());
5463     SvGlobalName aClassID;
5464     bool const isValid(aClassID.MakeId(clsid));
5465     assert(isValid); (void)isValid;
5466 
5467     if (!SotExchange::IsChart(aClassID))
5468         return false;
5469 
5470     m_aPostponedCharts.push_back(PostponedChart(pSdrObj, rSize, pFlyFrameFormat));
5471     return true;
5472 }
5473 
5474 /*
5475  * Write chart hierarchy in w:drawing after end element of w:rPr tag.
5476  */
WritePostponedChart()5477 void DocxAttributeOutput::WritePostponedChart()
5478 {
5479     if (m_aPostponedCharts.empty())
5480         return;
5481 
5482     for (const PostponedChart& rChart : m_aPostponedCharts)
5483     {
5484         uno::Reference< chart2::XChartDocument > xChartDoc;
5485         uno::Reference< drawing::XShape > xShape(const_cast<SdrObject*>(rChart.object)->getUnoShape(), uno::UNO_QUERY );
5486         if( xShape.is() )
5487         {
5488             uno::Reference< beans::XPropertySet > xPropSet( xShape, uno::UNO_QUERY );
5489             if( xPropSet.is() )
5490                 xChartDoc.set( xPropSet->getPropertyValue( u"Model"_ustr ), uno::UNO_QUERY );
5491         }
5492 
5493         if( xChartDoc.is() )
5494         {
5495             SAL_INFO("sw.ww8", "DocxAttributeOutput::WriteOLE2Obj: export chart ");
5496 
5497             m_rExport.SdrExporter().startDMLAnchorInline(rChart.frame, rChart.size);
5498 
5499             OUString sName(u"Object 1"_ustr);
5500             uno::Reference< container::XNamed > xNamed( xShape, uno::UNO_QUERY );
5501             if( xNamed.is() )
5502                 sName = xNamed->getName();
5503 
5504             // tdf#153203  export a11y related properties
5505             uno::Reference<beans::XPropertySet> xShapeProps(xShape, uno::UNO_QUERY);
5506             OUString const title(xShapeProps->getPropertyValue(u"Title"_ustr).get<OUString>());
5507             OUString const descr(xShapeProps->getPropertyValue(u"Description"_ustr).get<OUString>());
5508 
5509             /* If there is a scenario where a chart is followed by a shape
5510                which is being exported as an alternate content then, the
5511                docPr Id is being repeated, ECMA 20.4.2.5 says that the
5512                docPr Id should be unique, ensuring the same here.
5513                */
5514             auto const docPrattrList(CreateDocPrAttrList(
5515                 GetExport(), m_anchorId++, sName, title, descr));
5516             m_pSerializer->singleElementNS(XML_wp, XML_docPr, docPrattrList);
5517 
5518             m_pSerializer->singleElementNS(XML_wp, XML_cNvGraphicFramePr);
5519 
5520             m_pSerializer->startElementNS( XML_a, XML_graphic,
5521                     FSNS( XML_xmlns, XML_a ), GetExport().GetFilter().getNamespaceURL(OOX_NS(dml)) );
5522 
5523             m_pSerializer->startElementNS( XML_a, XML_graphicData,
5524                     XML_uri, "http://schemas.openxmlformats.org/drawingml/2006/chart" );
5525 
5526             OString aRelId;
5527             m_nChartCount++;
5528             aRelId = m_rExport.OutputChart( xChartDoc, m_nChartCount, m_pSerializer );
5529 
5530             m_pSerializer->singleElementNS( XML_c, XML_chart,
5531                     FSNS( XML_xmlns, XML_c ), GetExport().GetFilter().getNamespaceURL(OOX_NS(dmlChart)),
5532                     FSNS( XML_xmlns, XML_r ), GetExport().GetFilter().getNamespaceURL(OOX_NS(officeRel)),
5533                     FSNS( XML_r, XML_id ), aRelId );
5534 
5535             m_pSerializer->endElementNS( XML_a, XML_graphicData );
5536             m_pSerializer->endElementNS( XML_a, XML_graphic );
5537 
5538             m_rExport.SdrExporter().endDMLAnchorInline(rChart.frame);
5539         }
5540     }
5541 
5542     m_aPostponedCharts.clear();
5543 }
5544 
WriteOLEMath(const SwOLENode & rOLENode,const sal_Int8 nAlign)5545 bool DocxAttributeOutput::WriteOLEMath( const SwOLENode& rOLENode ,const sal_Int8 nAlign)
5546 {
5547     uno::Reference < embed::XEmbeddedObject > xObj(const_cast<SwOLENode&>(rOLENode).GetOLEObj().GetOleRef());
5548     SvGlobalName aObjName(xObj->getClassID());
5549 
5550     if( !SotExchange::IsMath(aObjName) )
5551         return false;
5552 
5553     try
5554     {
5555         PostponedMathObjects aPostponedMathObject;
5556         aPostponedMathObject.pMathObject = const_cast<SwOLENode*>( &rOLENode);
5557         aPostponedMathObject.nMathObjAlignment = nAlign;
5558         m_aPostponedMaths.push_back(aPostponedMathObject);
5559     }
5560     catch (const uno::Exception&)
5561     {
5562     }
5563     return true;
5564 }
5565 
WritePostponedMath(const SwOLENode * pPostponedMath,sal_Int8 nAlign)5566 void DocxAttributeOutput::WritePostponedMath(const SwOLENode* pPostponedMath, sal_Int8 nAlign)
5567 {
5568     uno::Reference < embed::XEmbeddedObject > xObj(const_cast<SwOLENode*>(pPostponedMath)->GetOLEObj().GetOleRef());
5569     if (embed::EmbedStates::LOADED == xObj->getCurrentState())
5570     {
5571         // must be running so there is a Component
5572         try
5573         {
5574             xObj->changeState(embed::EmbedStates::RUNNING);
5575         }
5576         catch (const uno::Exception&)
5577         {
5578         }
5579     }
5580     uno::Reference< uno::XInterface > xInterface( xObj->getComponent(), uno::UNO_QUERY );
5581     if (!xInterface.is())
5582     {
5583         SAL_WARN("sw.ww8", "Broken math object");
5584         return;
5585     }
5586     if( oox::FormulaImExportBase* formulaexport = dynamic_cast< oox::FormulaImExportBase* >( xInterface.get()))
5587         formulaexport->writeFormulaOoxml( m_pSerializer, GetExport().GetFilter().getVersion(),
5588                 oox::drawingml::DOCUMENT_DOCX, nAlign);
5589     else
5590         OSL_FAIL( "Math OLE object cannot write out OOXML" );
5591 }
5592 
WritePostponedFormControl(const SdrObject * pObject)5593 void DocxAttributeOutput::WritePostponedFormControl(const SdrObject* pObject)
5594 {
5595     if (!pObject || pObject->GetObjInventor() != SdrInventor::FmForm)
5596         return;
5597 
5598     SdrUnoObj *pFormObj = const_cast<SdrUnoObj*>(dynamic_cast< const SdrUnoObj*>(pObject));
5599     if (!pFormObj)
5600         return;
5601 
5602     uno::Reference<awt::XControlModel> xControlModel = pFormObj->GetUnoControlModel();
5603     uno::Reference<lang::XServiceInfo> xInfo(xControlModel, uno::UNO_QUERY);
5604     if (!xInfo.is())
5605         return;
5606 
5607     if (xInfo->supportsService(u"com.sun.star.form.component.DateField"_ustr))
5608     {
5609         // gather component properties
5610 
5611         OUString sDateFormat;
5612         uno::Reference<beans::XPropertySet> xPropertySet(xControlModel, uno::UNO_QUERY);
5613 
5614         OString sDate;
5615         OUString aContentText;
5616         bool bHasDate = false;
5617         css::util::Date aUNODate;
5618         if (xPropertySet->getPropertyValue(u"Date"_ustr) >>= aUNODate)
5619         {
5620             bHasDate = true;
5621             Date aDate(aUNODate.Day, aUNODate.Month, aUNODate.Year);
5622             sDate = DateToOString(aDate);
5623             aContentText = DateToDDMMYYYYOUString(aDate);
5624             sDateFormat = "dd/MM/yyyy";
5625         }
5626         else
5627         {
5628             aContentText = xPropertySet->getPropertyValue(u"HelpText"_ustr).get<OUString>();
5629             if(sDateFormat.isEmpty())
5630                 sDateFormat = "dd/MM/yyyy"; // Need to set date format even if there is no date set
5631         }
5632 
5633         // output component
5634 
5635         m_pSerializer->startElementNS(XML_w, XML_sdt);
5636         m_pSerializer->startElementNS(XML_w, XML_sdtPr);
5637 
5638         if (bHasDate)
5639             m_pSerializer->startElementNS(XML_w, XML_date, FSNS(XML_w, XML_fullDate), sDate);
5640         else
5641             m_pSerializer->startElementNS(XML_w, XML_date);
5642 
5643         m_pSerializer->singleElementNS(XML_w, XML_dateFormat, FSNS(XML_w, XML_val), sDateFormat);
5644         m_pSerializer->singleElementNS(XML_w, XML_lid,
5645                                        FSNS(XML_w, XML_val), "en-US");
5646         m_pSerializer->singleElementNS(XML_w, XML_storeMappedDataAs,
5647                                        FSNS(XML_w, XML_val), "dateTime");
5648         m_pSerializer->singleElementNS(XML_w, XML_calendar,
5649                                        FSNS(XML_w, XML_val), "gregorian");
5650 
5651         m_pSerializer->endElementNS(XML_w, XML_date);
5652         m_pSerializer->endElementNS(XML_w, XML_sdtPr);
5653 
5654         m_pSerializer->startElementNS(XML_w, XML_sdtContent);
5655         m_pSerializer->startElementNS(XML_w, XML_r);
5656 
5657         RunText(aContentText);
5658         m_pSerializer->endElementNS(XML_w, XML_r);
5659         m_pSerializer->endElementNS(XML_w, XML_sdtContent);
5660 
5661         m_pSerializer->endElementNS(XML_w, XML_sdt);
5662     }
5663     else if (xInfo->supportsService(u"com.sun.star.form.component.ComboBox"_ustr))
5664     {
5665         // gather component properties
5666 
5667         uno::Reference<beans::XPropertySet> xPropertySet(xControlModel, uno::UNO_QUERY);
5668         OUString sText = xPropertySet->getPropertyValue(u"Text"_ustr).get<OUString>();
5669         const uno::Sequence<OUString> aItems = xPropertySet->getPropertyValue(u"StringItemList"_ustr).get< uno::Sequence<OUString> >();
5670 
5671         // output component
5672 
5673         m_pSerializer->startElementNS(XML_w, XML_sdt);
5674         m_pSerializer->startElementNS(XML_w, XML_sdtPr);
5675 
5676         m_pSerializer->startElementNS(XML_w, XML_dropDownList);
5677 
5678         for (const auto& rItem : aItems)
5679         {
5680             m_pSerializer->singleElementNS(XML_w, XML_listItem,
5681                                            FSNS(XML_w, XML_displayText), rItem,
5682                                            FSNS(XML_w, XML_value), rItem);
5683         }
5684 
5685         m_pSerializer->endElementNS(XML_w, XML_dropDownList);
5686         m_pSerializer->endElementNS(XML_w, XML_sdtPr);
5687 
5688         m_pSerializer->startElementNS(XML_w, XML_sdtContent);
5689         m_pSerializer->startElementNS(XML_w, XML_r);
5690         RunText(sText);
5691         m_pSerializer->endElementNS(XML_w, XML_r);
5692         m_pSerializer->endElementNS(XML_w, XML_sdtContent);
5693 
5694         m_pSerializer->endElementNS(XML_w, XML_sdt);
5695     }
5696 }
5697 
WritePostponedActiveXControl(bool bInsideRun)5698 void DocxAttributeOutput::WritePostponedActiveXControl(bool bInsideRun)
5699 {
5700     for( const auto & rPostponedDrawing : m_aPostponedActiveXControls )
5701     {
5702         WriteActiveXControl(rPostponedDrawing.object, *rPostponedDrawing.frame, bInsideRun);
5703     }
5704     m_aPostponedActiveXControls.clear();
5705 }
5706 
5707 
WriteActiveXControl(const SdrObject * pObject,const SwFrameFormat & rFrameFormat,bool bInsideRun)5708 void DocxAttributeOutput::WriteActiveXControl(const SdrObject* pObject, const SwFrameFormat& rFrameFormat, bool bInsideRun)
5709 {
5710     SdrUnoObj *pFormObj = const_cast<SdrUnoObj*>(dynamic_cast< const SdrUnoObj*>(pObject));
5711     if (!pFormObj)
5712         return;
5713 
5714     uno::Reference<awt::XControlModel> xControlModel = pFormObj->GetUnoControlModel();
5715     if (!xControlModel.is())
5716         return;
5717 
5718     const bool bAnchoredInline = rFrameFormat.GetAnchor().GetAnchorId() == static_cast<RndStdIds>(css::text::TextContentAnchorType_AS_CHARACTER);
5719 
5720     if(!bInsideRun)
5721     {
5722         m_pSerializer->startElementNS(XML_w, XML_r);
5723     }
5724 
5725     // w:pict for floating embedded control and w:object for inline embedded control
5726     if(bAnchoredInline)
5727         m_pSerializer->startElementNS(XML_w, XML_object);
5728     else
5729         m_pSerializer->startElementNS(XML_w, XML_pict);
5730 
5731     // write ActiveX fragment and ActiveX binary
5732     uno::Reference<drawing::XShape> xShape(const_cast<SdrObject*>(pObject)->getUnoShape(), uno::UNO_QUERY);
5733     std::pair<OString,OString> sRelIdAndName = m_rExport.WriteActiveXObject(xShape, xControlModel);
5734 
5735     // VML shape definition
5736     m_rExport.VMLExporter().SetSkipwzName(true);
5737     m_rExport.VMLExporter().SetHashMarkForType(true);
5738     m_rExport.VMLExporter().OverrideShapeIDGen(true, "control_shape_"_ostr);
5739     OString sShapeId;
5740     if(bAnchoredInline)
5741     {
5742         sShapeId = m_rExport.VMLExporter().AddInlineSdrObject(*pObject, true);
5743     }
5744     else
5745     {
5746         SwFormatFollowTextFlow const& rFlow(rFrameFormat.GetFollowTextFlow());
5747         const SwFormatHoriOrient& rHoriOri = rFrameFormat.GetHoriOrient();
5748         const SwFormatVertOrient& rVertOri = rFrameFormat.GetVertOrient();
5749         SwFormatSurround const& rSurround(rFrameFormat.GetSurround());
5750         rtl::Reference<sax_fastparser::FastAttributeList> pAttrList(docx::SurroundToVMLWrap(rSurround));
5751         sShapeId = m_rExport.VMLExporter().AddSdrObject(*pObject,
5752             rFlow.GetValue(),
5753             rHoriOri.GetHoriOrient(), rVertOri.GetVertOrient(),
5754             rHoriOri.GetRelationOrient(),
5755             rVertOri.GetRelationOrient(),
5756             pAttrList.get(),
5757             true);
5758     }
5759     // Restore default values
5760     m_rExport.VMLExporter().SetSkipwzName(false);
5761     m_rExport.VMLExporter().SetHashMarkForType(false);
5762     m_rExport.VMLExporter().OverrideShapeIDGen(false);
5763 
5764     // control
5765     m_pSerializer->singleElementNS(XML_w, XML_control,
5766                                     FSNS(XML_r, XML_id), sRelIdAndName.first,
5767                                     FSNS(XML_w, XML_name), sRelIdAndName.second,
5768                                     FSNS(XML_w, XML_shapeid), sShapeId);
5769 
5770     if(bAnchoredInline)
5771         m_pSerializer->endElementNS(XML_w, XML_object);
5772     else
5773         m_pSerializer->endElementNS(XML_w, XML_pict);
5774 
5775     if(!bInsideRun)
5776     {
5777         m_pSerializer->endElementNS(XML_w, XML_r);
5778     }
5779 }
5780 
ExportAsActiveXControl(const SdrObject * pObject) const5781 bool DocxAttributeOutput::ExportAsActiveXControl(const SdrObject* pObject) const
5782 {
5783     SdrUnoObj *pFormObj = const_cast<SdrUnoObj*>(dynamic_cast< const SdrUnoObj*>(pObject));
5784     if (!pFormObj)
5785         return false;
5786 
5787     uno::Reference<awt::XControlModel> xControlModel = pFormObj->GetUnoControlModel();
5788     if (!xControlModel.is())
5789         return false;
5790 
5791     uno::Reference< css::frame::XModel > xModel( m_rExport.m_rDoc.GetDocShell() ? m_rExport.m_rDoc.GetDocShell()->GetModel() : nullptr );
5792     if (!xModel.is())
5793         return false;
5794 
5795     uno::Reference<lang::XServiceInfo> xInfo(xControlModel, uno::UNO_QUERY);
5796     if (!xInfo.is())
5797         return false;
5798 
5799     // See WritePostponedFormControl
5800     // By now date field and combobox is handled on a different way, so let's not interfere with the other method.
5801     if(xInfo->supportsService(u"com.sun.star.form.component.DateField"_ustr) ||
5802        xInfo->supportsService(u"com.sun.star.form.component.ComboBox"_ustr))
5803         return false;
5804 
5805     oox::ole::OleFormCtrlExportHelper exportHelper(comphelper::getProcessComponentContext(), xModel, xControlModel);
5806     return exportHelper.isValid();
5807 }
5808 
PostponeOLE(SwOLENode & rNode,const Size & rSize,const SwFlyFrameFormat * pFlyFrameFormat)5809 void DocxAttributeOutput::PostponeOLE( SwOLENode& rNode, const Size& rSize, const SwFlyFrameFormat* pFlyFrameFormat )
5810 {
5811     if( !m_oPostponedOLEs )
5812         //cannot be postponed, try to write now
5813         WriteOLE( rNode, rSize, pFlyFrameFormat );
5814     else
5815         m_oPostponedOLEs->push_back( PostponedOLE( &rNode, rSize, pFlyFrameFormat ) );
5816 }
5817 
5818 /*
5819  * Write w:object hierarchy for embedded objects after end element of w:rPr tag.
5820  */
WritePostponedOLE()5821 void DocxAttributeOutput::WritePostponedOLE()
5822 {
5823     if( !m_oPostponedOLEs )
5824         return;
5825 
5826     for( const auto & rPostponedOLE : *m_oPostponedOLEs )
5827     {
5828         WriteOLE( *rPostponedOLE.object, rPostponedOLE.size, rPostponedOLE.frame );
5829     }
5830 
5831     // clear list of postponed objects
5832     m_oPostponedOLEs.reset();
5833 }
5834 
WriteOLE(SwOLENode & rNode,const Size & rSize,const SwFlyFrameFormat * pFlyFrameFormat)5835 void DocxAttributeOutput::WriteOLE( SwOLENode& rNode, const Size& rSize, const SwFlyFrameFormat* pFlyFrameFormat )
5836 {
5837     OSL_ASSERT(pFlyFrameFormat);
5838 
5839     // get interoperability information about embedded objects
5840     uno::Reference< beans::XPropertySet > xPropSet( m_rExport.m_rDoc.GetDocShell()->GetBaseModel(), uno::UNO_QUERY_THROW );
5841     uno::Sequence< beans::PropertyValue > aGrabBag, aObjectsInteropList,aObjectInteropAttributes;
5842     xPropSet->getPropertyValue( UNO_NAME_MISC_OBJ_INTEROPGRABBAG ) >>= aGrabBag;
5843     auto pProp = std::find_if(std::cbegin(aGrabBag), std::cend(aGrabBag),
5844         [](const beans::PropertyValue& rProp) { return rProp.Name == "EmbeddedObjects"; });
5845     if (pProp != std::cend(aGrabBag))
5846         pProp->Value >>= aObjectsInteropList;
5847 
5848     SwOLEObj& aObject = rNode.GetOLEObj();
5849     uno::Reference < embed::XEmbeddedObject > xObj( aObject.GetOleRef() );
5850     comphelper::EmbeddedObjectContainer* aContainer = aObject.GetObject().GetContainer();
5851     OUString sObjectName = aContainer->GetEmbeddedObjectName( xObj );
5852 
5853     // set some attributes according to the type of the embedded object
5854     OUString sProgID, sDrawAspect;
5855     switch (rNode.GetAspect())
5856     {
5857         case embed::Aspects::MSOLE_CONTENT: sDrawAspect = "Content"; break;
5858         case embed::Aspects::MSOLE_DOCPRINT: sDrawAspect = "DocPrint"; break;
5859         case embed::Aspects::MSOLE_ICON: sDrawAspect = "Icon"; break;
5860         case embed::Aspects::MSOLE_THUMBNAIL: sDrawAspect = "Thumbnail"; break;
5861         default:
5862             SAL_WARN("sw.ww8", "DocxAttributeOutput::WriteOLE: invalid aspect value");
5863     }
5864     auto pObjectsInterop = std::find_if(std::cbegin(aObjectsInteropList), std::cend(aObjectsInteropList),
5865         [&sObjectName](const beans::PropertyValue& rProp) { return rProp.Name == sObjectName; });
5866     if (pObjectsInterop != std::cend(aObjectsInteropList))
5867         pObjectsInterop->Value >>= aObjectInteropAttributes;
5868 
5869     for (const auto& rObjectInteropAttribute : aObjectInteropAttributes)
5870     {
5871         if ( rObjectInteropAttribute.Name == "ProgID" )
5872         {
5873             rObjectInteropAttribute.Value >>= sProgID;
5874         }
5875     }
5876 
5877     // write embedded file
5878     OString sId = m_rExport.WriteOLEObject(aObject, sProgID);
5879 
5880     if( sId.isEmpty() )
5881     {
5882         // the embedded file could not be saved
5883         // fallback: save as an image
5884         FlyFrameGraphic( nullptr, rSize, pFlyFrameFormat, &rNode );
5885         return;
5886     }
5887 
5888     // write preview image
5889     const Graphic* pGraphic = rNode.GetGraphic();
5890     m_rDrawingML.SetFS(m_pSerializer);
5891     OUString sImageId = m_rDrawingML.writeGraphicToStorage(*pGraphic);
5892 
5893     if ( sDrawAspect == "Content" )
5894     {
5895         try
5896         {
5897             awt::Size aSize = xObj->getVisualAreaSize( rNode.GetAspect() );
5898 
5899             MapUnit aUnit = VCLUnoHelper::UnoEmbed2VCLMapUnit( xObj->getMapUnit( rNode.GetAspect() ) );
5900             Size aOriginalSize( OutputDevice::LogicToLogic(Size( aSize.Width, aSize.Height),
5901                                                 MapMode(aUnit), MapMode(MapUnit::MapTwip)));
5902 
5903             m_pSerializer->startElementNS( XML_w, XML_object,
5904                                    FSNS(XML_w, XML_dxaOrig), OString::number(aOriginalSize.Width()),
5905                                    FSNS(XML_w, XML_dyaOrig), OString::number(aOriginalSize.Height()) );
5906         }
5907         catch ( uno::Exception& )
5908         {
5909             m_pSerializer->startElementNS(XML_w, XML_object);
5910         }
5911     }
5912     else
5913     {
5914         m_pSerializer->startElementNS(XML_w, XML_object);
5915     }
5916 
5917     OString sShapeId = "ole_" + sId;
5918 
5919     //OLE Shape definition
5920     WriteOLEShape(*pFlyFrameFormat, rSize, sShapeId, sImageId);
5921 
5922     //OLE Object definition
5923     m_pSerializer->singleElementNS(XML_o, XML_OLEObject,
5924                                    XML_Type, "Embed",
5925                                    XML_ProgID, sProgID,
5926                                    XML_ShapeID, sShapeId,
5927                                    XML_DrawAspect, sDrawAspect,
5928                                    XML_ObjectID, "_" + OString::number(comphelper::rng::uniform_int_distribution(0, std::numeric_limits<int>::max())),
5929                                    FSNS( XML_r, XML_id ), sId );
5930 
5931     m_pSerializer->endElementNS(XML_w, XML_object);
5932 }
5933 
WriteOLEShape(const SwFlyFrameFormat & rFrameFormat,const Size & rSize,std::string_view rShapeId,const OUString & rImageId)5934 void DocxAttributeOutput::WriteOLEShape(const SwFlyFrameFormat& rFrameFormat, const Size& rSize,
5935                                         std::string_view rShapeId, const OUString& rImageId)
5936 {
5937     assert(m_pSerializer);
5938 
5939     //Here is an attribute list where we collect the attributes what we want to export
5940     rtl::Reference<FastAttributeList> pAttr = FastSerializerHelper::createAttrList();
5941     pAttr->add(XML_id, rShapeId);
5942 
5943     //export the fixed shape type for picture frame
5944     m_pSerializer->write(vml::VMLExport::GetVMLShapeTypeDefinition(rShapeId, true));
5945     pAttr->add(XML_type, OString::Concat("_x0000_t") + rShapeId);
5946 
5947     //Export the style attribute for position and size
5948     pAttr->add(XML_style, GetOLEStyle(rFrameFormat, rSize));
5949     //Get the OLE frame
5950     const SvxBoxItem& rBox = rFrameFormat.GetAttrSet().GetBox();
5951     OString sLineType;
5952     OString sDashType;
5953     //Word does not handle differently the four sides,
5954     //so we have to choose, and the left one is the winner:
5955     if (rBox.GetLeft())
5956     {
5957         //Get the left border color and width
5958         const Color aLineColor = rBox.GetLeft()->GetColor();
5959         const tools::Long aLineWidth = rBox.GetLeft()->GetWidth();
5960 
5961         //Convert the left OLE border style to OOXML
5962         //FIXME improve if it's necessary
5963         switch (rBox.GetLeft()->GetBorderLineStyle())
5964         {
5965             case SvxBorderLineStyle::SOLID:
5966                 sLineType = "Single"_ostr;
5967                 sDashType = "Solid"_ostr;
5968                 break;
5969             case SvxBorderLineStyle::DASHED:
5970                 sLineType = "Single"_ostr;
5971                 sDashType = "Dash"_ostr;
5972                 break;
5973             case SvxBorderLineStyle::DASH_DOT:
5974                 sLineType = "Single"_ostr;
5975                 sDashType = "DashDot"_ostr;
5976                 break;
5977             case SvxBorderLineStyle::DASH_DOT_DOT:
5978                 sLineType = "Single"_ostr;
5979                 sDashType = "ShortDashDotDot"_ostr;
5980                 break;
5981             case SvxBorderLineStyle::DOTTED:
5982                 sLineType = "Single"_ostr;
5983                 sDashType = "Dot"_ostr;
5984                 break;
5985             case SvxBorderLineStyle::DOUBLE:
5986                 sLineType = "ThinThin"_ostr;
5987                 sDashType = "Solid"_ostr;
5988                 break;
5989             case SvxBorderLineStyle::DOUBLE_THIN:
5990                 sLineType = "ThinThin"_ostr;
5991                 sDashType = "Solid"_ostr;
5992                 break;
5993             case SvxBorderLineStyle::EMBOSSED:
5994                 sLineType = "Single"_ostr;
5995                 sDashType = "Solid"_ostr;
5996                 break;
5997             case SvxBorderLineStyle::ENGRAVED:
5998                 sLineType = "Single"_ostr;
5999                 sDashType = "Solid"_ostr;
6000                 break;
6001             case SvxBorderLineStyle::FINE_DASHED:
6002                 sLineType = "Single"_ostr;
6003                 sDashType = "Dot"_ostr;
6004                 break;
6005             case SvxBorderLineStyle::INSET:
6006                 sLineType = "Single"_ostr;
6007                 sDashType = "Solid"_ostr;
6008                 break;
6009             case SvxBorderLineStyle::OUTSET:
6010                 sLineType = "Single"_ostr;
6011                 sDashType = "Solid"_ostr;
6012                 break;
6013             case SvxBorderLineStyle::THICKTHIN_LARGEGAP:
6014             case SvxBorderLineStyle::THICKTHIN_MEDIUMGAP:
6015             case SvxBorderLineStyle::THICKTHIN_SMALLGAP:
6016                 sLineType = "ThickThin"_ostr;
6017                 sDashType = "Solid"_ostr;
6018                 break;
6019             case SvxBorderLineStyle::THINTHICK_LARGEGAP:
6020             case SvxBorderLineStyle::THINTHICK_MEDIUMGAP:
6021             case SvxBorderLineStyle::THINTHICK_SMALLGAP:
6022                 sLineType = "ThinThick"_ostr;
6023                 sDashType = "Solid"_ostr;
6024                 break;
6025             case SvxBorderLineStyle::NONE:
6026                 sLineType = ""_ostr;
6027                 sDashType = ""_ostr;
6028                 break;
6029             default:
6030                 SAL_WARN("sw.ww8", "Unknown line type on OOXML ELE export!");
6031                 break;
6032         }
6033 
6034         //If there is a line add it for export
6035         if (!sLineType.isEmpty() && !sDashType.isEmpty())
6036         {
6037             pAttr->add(XML_stroked, "t");
6038             pAttr->add(XML_strokecolor, "#" + msfilter::util::ConvertColor(aLineColor));
6039             pAttr->add(XML_strokeweight, OString::number(aLineWidth / 20) + "pt");
6040         }
6041     }
6042 
6043     //Let's check the filltype of the OLE
6044     switch (rFrameFormat.GetAttrSet().Get(XATTR_FILLSTYLE).GetValue())
6045     {
6046         case drawing::FillStyle::FillStyle_SOLID:
6047         {
6048             //If solid, we get the color and add it to the exporter
6049             const Color rShapeColor = rFrameFormat.GetAttrSet().Get(XATTR_FILLCOLOR).GetColorValue();
6050             pAttr->add(XML_filled, "t");
6051             pAttr->add(XML_fillcolor, "#" + msfilter::util::ConvertColor(rShapeColor));
6052             break;
6053         }
6054         case drawing::FillStyle::FillStyle_GRADIENT:
6055         case drawing::FillStyle::FillStyle_HATCH:
6056         case drawing::FillStyle::FillStyle_BITMAP:
6057             //TODO
6058             break;
6059         case drawing::FillStyle::FillStyle_NONE:
6060         {
6061             pAttr->add(XML_filled, "f");
6062             break;
6063         }
6064         default:
6065             SAL_WARN("sw.ww8", "Unknown fill type on OOXML OLE export!");
6066             break;
6067     }
6068     pAttr->addNS(XML_o, XML_ole, ""); //compulsory, even if it's empty
6069     m_pSerializer->startElementNS(XML_v, XML_shape, pAttr);//Write the collected attrs...
6070 
6071     if (!sLineType.isEmpty() && !sDashType.isEmpty()) //If there is a line/dash style it is time to export it
6072     {
6073         m_pSerializer->singleElementNS(XML_v, XML_stroke, XML_linestyle, sLineType, XML_dashstyle, sDashType);
6074     }
6075 
6076     // shape filled with the preview image
6077     m_pSerializer->singleElementNS(XML_v, XML_imagedata,
6078                                    FSNS(XML_r, XML_id), rImageId,
6079                                    FSNS(XML_o, XML_title), "");
6080 
6081     //export wrap settings
6082     if (rFrameFormat.GetAnchor().GetAnchorId() != RndStdIds::FLY_AS_CHAR) //As-char objs does not have surround.
6083         ExportOLESurround(rFrameFormat.GetSurround());
6084 
6085     m_pSerializer->endElementNS(XML_v, XML_shape);
6086 }
6087 
GetOLEStyle(const SwFlyFrameFormat & rFormat,const Size & rSize)6088 OString DocxAttributeOutput::GetOLEStyle(const SwFlyFrameFormat& rFormat, const Size& rSize)
6089 {
6090     //tdf#131539: Export OLE positions in docx:
6091     //This string will store the position output for the xml
6092     OString aPos;
6093     //This string will store the relative position for aPos
6094     OString aAnch;
6095 
6096     if (rFormat.GetAnchor().GetAnchorId() != RndStdIds::FLY_AS_CHAR)
6097     {
6098         //Get the horizontal alignment of the OLE via the frame format, to aHAlign
6099         OString aHAlign = convertToOOXMLHoriOrient(rFormat.GetHoriOrient().GetHoriOrient(),
6100             rFormat.GetHoriOrient().IsPosToggle());
6101         //Get the vertical alignment of the OLE via the frame format to aVAlign
6102         OString aVAlign = convertToOOXMLVertOrient(rFormat.GetVertOrient().GetVertOrient());
6103 
6104         // Check if the OLE anchored to page:
6105         const bool bIsPageAnchor = rFormat.GetAnchor().GetAnchorId() == RndStdIds::FLY_AT_PAGE;
6106 
6107         //Get the relative horizontal positions for the anchors
6108         OString aHAnch
6109             = bIsPageAnchor
6110                   ? "page"_ostr
6111                   : convertToOOXMLHoriOrientRel(rFormat.GetHoriOrient().GetRelationOrient());
6112         //Get the relative vertical positions for the anchors
6113         OString aVAnch = convertToOOXMLVertOrientRel(rFormat.GetVertOrient().GetRelationOrient());
6114 
6115         //Choice that the horizontal position is relative or not
6116         if (!aHAlign.isEmpty())
6117             aHAlign = ";mso-position-horizontal:" + aHAlign;
6118         aHAlign = ";mso-position-horizontal-relative:" + aHAnch;
6119 
6120         //Choice that the vertical position is relative or not
6121         if (!aVAlign.isEmpty())
6122             aVAlign = ";mso-position-vertical:" + aVAlign;
6123         aVAlign = ";mso-position-vertical-relative:" + aVAnch;
6124 
6125         //Set the anchoring information into one string for aPos
6126         aAnch = aHAlign + aVAlign;
6127 
6128         //Query the positions to aPos from frameformat
6129         aPos =
6130             "position:absolute;margin-left:" + OString::number(double(rFormat.GetHoriOrient().GetPos()) / 20) +
6131             "pt;margin-top:" + OString::number(double(rFormat.GetVertOrient().GetPos()) / 20) + "pt;";
6132     }
6133 
6134     OString sShapeStyle = "width:" + OString::number( double( rSize.Width() ) / 20 ) +
6135                         "pt;height:" + OString::number( double( rSize.Height() ) / 20 ) +
6136                         "pt"; //from VMLExport::AddRectangleDimensions(), it does: value/20
6137 
6138     const SvxLRSpaceItem& rLRSpace = rFormat.GetLRSpace();
6139     if (rLRSpace.IsExplicitZeroMarginValLeft() || rLRSpace.GetLeft())
6140         sShapeStyle += ";mso-wrap-distance-left:" + OString::number(double(rLRSpace.GetLeft()) / 20) + "pt";
6141     if (rLRSpace.IsExplicitZeroMarginValRight() || rLRSpace.GetRight())
6142         sShapeStyle += ";mso-wrap-distance-right:" + OString::number(double(rLRSpace.GetRight()) / 20) + "pt";
6143     const SvxULSpaceItem& rULSpace = rFormat.GetULSpace();
6144     if (rULSpace.GetUpper())
6145         sShapeStyle += ";mso-wrap-distance-top:" + OString::number(double(rULSpace.GetUpper()) / 20) + "pt";
6146     if (rULSpace.GetLower())
6147         sShapeStyle += ";mso-wrap-distance-bottom:" + OString::number(double(rULSpace.GetLower()) / 20) + "pt";
6148 
6149     //Export anchor setting, if it exists
6150     if (!aPos.isEmpty() && !aAnch.isEmpty())
6151         sShapeStyle = aPos + sShapeStyle  + aAnch;
6152 
6153     return sShapeStyle;
6154 }
6155 
ExportOLESurround(const SwFormatSurround & rWrap)6156 void DocxAttributeOutput::ExportOLESurround(const SwFormatSurround& rWrap)
6157 {
6158     const bool bIsContour = rWrap.IsContour(); //Has the shape contour or not
6159     OString sSurround;
6160     OString sSide;
6161 
6162     //Map the ODF wrap settings to OOXML one
6163     switch (rWrap.GetSurround())
6164     {
6165         case text::WrapTextMode::WrapTextMode_NONE:
6166             sSurround = "topAndBottom"_ostr;
6167             break;
6168         case text::WrapTextMode::WrapTextMode_PARALLEL:
6169             sSurround = bIsContour ? "tight"_ostr : "square"_ostr;
6170             break;
6171         case text::WrapTextMode::WrapTextMode_DYNAMIC:
6172             sSide = "largest"_ostr;
6173             sSurround = bIsContour ? "tight"_ostr : "square"_ostr;
6174             break;
6175         case text::WrapTextMode::WrapTextMode_LEFT:
6176             sSide = "left"_ostr;
6177             sSurround = bIsContour ? "tight"_ostr : "square"_ostr;
6178             break;
6179         case text::WrapTextMode::WrapTextMode_RIGHT:
6180             sSide = "right"_ostr;
6181             sSurround = bIsContour ? "tight"_ostr : "square"_ostr;
6182             break;
6183         default:
6184             SAL_WARN("sw.ww8", "Unknown surround type on OOXML export!");
6185             break;
6186     }
6187 
6188     //if there is a setting export it:
6189     if (!sSurround.isEmpty())
6190     {
6191         if (sSide.isEmpty())
6192             m_pSerializer->singleElementNS(XML_w10, XML_wrap, XML_type, sSurround);
6193         else
6194             m_pSerializer->singleElementNS(XML_w10, XML_wrap, XML_type, sSurround, XML_side, sSide);
6195     }
6196 }
6197 
WritePostponedCustomShape()6198 void DocxAttributeOutput::WritePostponedCustomShape()
6199 {
6200     if (!m_oPostponedCustomShape)
6201         return;
6202 
6203     for( const auto & rPostponedDrawing : *m_oPostponedCustomShape)
6204     {
6205         m_rExport.GetFilter().SetMaxDocId(m_anchorId + 1);
6206         if ( IsAlternateContentChoiceOpen() )
6207             m_rExport.SdrExporter().writeDMLDrawing(rPostponedDrawing.object, rPostponedDrawing.frame, m_anchorId++);
6208         else
6209             m_rExport.SdrExporter().writeDMLAndVMLDrawing(rPostponedDrawing.object, *rPostponedDrawing.frame, m_anchorId++);
6210         m_anchorId = m_rExport.GetFilter().GetMaxDocId();
6211     }
6212     m_oPostponedCustomShape.reset();
6213 }
6214 
WritePostponedDMLDrawing()6215 void DocxAttributeOutput::WritePostponedDMLDrawing()
6216 {
6217     if (!m_oPostponedDMLDrawings)
6218         return;
6219 
6220     // Clear the list early, this method may be called recursively.
6221     std::optional< std::vector<PostponedDrawing> > pPostponedDMLDrawings(std::move(m_oPostponedDMLDrawings));
6222     std::optional< std::vector<PostponedOLE> > pPostponedOLEs(std::move(m_oPostponedOLEs));
6223     m_oPostponedDMLDrawings.reset();
6224     m_oPostponedOLEs.reset();
6225 
6226     for( const auto & rPostponedDrawing : *pPostponedDMLDrawings )
6227     {
6228         // Avoid w:drawing within another w:drawing.
6229         m_rExport.GetFilter().SetMaxDocId(m_anchorId + 1);
6230         if ( IsAlternateContentChoiceOpen() && !( m_rExport.SdrExporter().IsDrawingOpen()) )
6231            m_rExport.SdrExporter().writeDMLDrawing(rPostponedDrawing.object, rPostponedDrawing.frame, m_anchorId++);
6232         else
6233             m_rExport.SdrExporter().writeDMLAndVMLDrawing(rPostponedDrawing.object, *rPostponedDrawing.frame, m_anchorId++);
6234         m_anchorId = m_rExport.GetFilter().GetMaxDocId();
6235     }
6236 
6237     m_oPostponedOLEs = std::move(pPostponedOLEs);
6238 }
6239 
WriteFlyFrame(const ww8::Frame & rFrame)6240 void DocxAttributeOutput::WriteFlyFrame(const ww8::Frame& rFrame)
6241 {
6242     m_pSerializer->mark(Tag_OutputFlyFrame);
6243 
6244     switch ( rFrame.GetWriterType() )
6245     {
6246         case ww8::Frame::eGraphic:
6247             {
6248                 const SdrObject* pSdrObj = rFrame.GetFrameFormat().FindRealSdrObject();
6249                 const SwNode *pNode = rFrame.GetContent();
6250                 const SwGrfNode *pGrfNode = pNode ? pNode->GetGrfNode() : nullptr;
6251                 if ( pGrfNode )
6252                 {
6253                     if (!m_oPostponedGraphic)
6254                     {
6255                         m_bPostponedProcessingFly = false ;
6256                         FlyFrameGraphic( pGrfNode, rFrame.GetLayoutSize(), nullptr, nullptr, pSdrObj);
6257                     }
6258                     else // we are writing out attributes, but w:drawing should not be inside w:rPr,
6259                     {    // so write it out later
6260                         m_bPostponedProcessingFly = true ;
6261                         m_oPostponedGraphic->push_back(PostponedGraphic(pGrfNode, rFrame.GetLayoutSize(), pSdrObj));
6262                     }
6263                 }
6264             }
6265             break;
6266         case ww8::Frame::eDrawing:
6267             {
6268                 const SdrObject* pSdrObj = rFrame.GetFrameFormat().FindRealSdrObject();
6269                 if ( pSdrObj )
6270                 {
6271                     const bool bIsDiagram(nullptr != pSdrObj && pSdrObj->isDiagram());
6272 
6273                     if (bIsDiagram)
6274                     {
6275                         if ( !m_oPostponedDiagrams )
6276                         {
6277                             m_bPostponedProcessingFly = false ;
6278                             m_rExport.SdrExporter().writeDiagram( pSdrObj, rFrame.GetFrameFormat(), m_anchorId++);
6279                         }
6280                         else // we are writing out attributes, but w:drawing should not be inside w:rPr,
6281                         {    // so write it out later
6282                             m_bPostponedProcessingFly = true ;
6283                             m_oPostponedDiagrams->push_back( PostponedDiagram( pSdrObj, &(rFrame.GetFrameFormat()) ));
6284                         }
6285                     }
6286                     else
6287                     {
6288                         if (!m_oPostponedDMLDrawings)
6289                         {
6290                             m_rExport.GetFilter().SetMaxDocId(m_anchorId + 1);
6291                             if ( IsAlternateContentChoiceOpen() )
6292                             {
6293                                 // Do not write w:drawing inside w:drawing. Instead Postpone the Inner Drawing.
6294                                 if( m_rExport.SdrExporter().IsDrawingOpen() )
6295                                     m_oPostponedCustomShape->push_back(PostponedDrawing(pSdrObj, &(rFrame.GetFrameFormat())));
6296                                 else
6297                                     m_rExport.SdrExporter().writeDMLDrawing( pSdrObj, &rFrame.GetFrameFormat(), m_anchorId++);
6298                             }
6299                             else
6300                                 m_rExport.SdrExporter().writeDMLAndVMLDrawing( pSdrObj, rFrame.GetFrameFormat(), m_anchorId++);
6301                             m_anchorId = m_rExport.GetFilter().GetMaxDocId();
6302 
6303                             m_bPostponedProcessingFly = false ;
6304                         }
6305                         // IsAlternateContentChoiceOpen(): check is to ensure that only one object is getting added. Without this check, plus one object gets added
6306                         // m_bParagraphFrameOpen: check if the frame is open.
6307                         else if (IsAlternateContentChoiceOpen() && m_bParagraphFrameOpen)
6308                             m_oPostponedCustomShape->push_back(PostponedDrawing(pSdrObj, &(rFrame.GetFrameFormat())));
6309                         else
6310                         {
6311                             // we are writing out attributes, but w:drawing should not be inside w:rPr, so write it out later
6312                             m_bPostponedProcessingFly = true ;
6313                             m_oPostponedDMLDrawings->push_back(PostponedDrawing(pSdrObj, &(rFrame.GetFrameFormat())));
6314                         }
6315                     }
6316                 }
6317             }
6318             break;
6319         case ww8::Frame::eTextBox:
6320             {
6321                 // If this is a TextBox of a shape, then ignore: it's handled in WriteTextBox().
6322                 if (DocxSdrExport::isTextBox(rFrame.GetFrameFormat()))
6323                     break;
6324 
6325                 // If this is a TextBox containing a table which we already exported directly, ignore it
6326                 if (m_aFloatingTablesOfParagraph.find(&rFrame.GetFrameFormat()) != m_aFloatingTablesOfParagraph.end())
6327                     break;
6328 
6329                 // The frame output is postponed to the end of the anchor paragraph
6330                 bool bDuplicate = false;
6331                 const OUString& rName = rFrame.GetFrameFormat().GetName();
6332                 if (m_aFramesOfParagraph.size() && !rName.isEmpty())
6333                 {
6334                     const unsigned nSize = m_aFramesOfParagraph.top().size();
6335                     for (unsigned nIndex = 0; nIndex < nSize; ++nIndex)
6336                     {
6337                         const OUString& rNameExisting
6338                             = m_aFramesOfParagraph.top()[nIndex].GetFrameFormat().GetName();
6339 
6340                         if (rName == rNameExisting)
6341                         {
6342                             bDuplicate = true;
6343                             break;
6344                         }
6345                     }
6346                 }
6347 
6348                 if( !bDuplicate )
6349                 {
6350                     m_bPostponedProcessingFly = true ;
6351                     if ( m_aFramesOfParagraph.size() )
6352                         m_aFramesOfParagraph.top().emplace_back(rFrame);
6353                 }
6354             }
6355             break;
6356         case ww8::Frame::eOle:
6357             {
6358                 const SwFrameFormat &rFrameFormat = rFrame.GetFrameFormat();
6359                 const SdrObject *pSdrObj = rFrameFormat.FindRealSdrObject();
6360                 if ( pSdrObj )
6361                 {
6362                     SwNodeIndex aIdx(*rFrameFormat.GetContent().GetContentIdx(), 1);
6363                     SwOLENode& rOLENd = *aIdx.GetNode().GetOLENode();
6364 
6365                     //output variable for the formula alignment (default inline)
6366                     sal_Int8 nAlign(FormulaImExportBase::eFormulaAlign::INLINE);
6367                     auto xObj(rOLENd.GetOLEObj().GetOleRef()); //get the xObject of the formula
6368 
6369                     //tdf133030: Export formula position
6370                     //If we have a formula with inline anchor...
6371                     if(SotExchange::IsMath(xObj->getClassID()) && rFrame.IsInline())
6372                     {
6373                         SwNode const* const pAnchorNode = rFrameFormat.GetAnchor().GetAnchorNode();
6374                         if(pAnchorNode)
6375                         {
6376                             //Get the text node what the formula anchored to
6377                             const SwTextNode* pTextNode = pAnchorNode->GetTextNode();
6378                             if(pTextNode && pTextNode->Len() == 1)
6379                             {
6380                                 //Get the paragraph alignment
6381                                 auto aParaAdjust = pTextNode->GetSwAttrSet().GetAdjust().GetAdjust();
6382                                 //And set the formula according to the paragraph alignment
6383                                 if (aParaAdjust == SvxAdjust::Center)
6384                                     nAlign = FormulaImExportBase::eFormulaAlign::CENTER;
6385                                 else if (aParaAdjust == SvxAdjust::Right)
6386                                     nAlign = FormulaImExportBase::eFormulaAlign::RIGHT;
6387                                 else // left in the case of left and justified paragraph alignments
6388                                     nAlign = FormulaImExportBase::eFormulaAlign::LEFT;
6389                             }
6390                         }
6391                     }
6392                     WriteOLE2Obj( pSdrObj, rOLENd, rFrame.GetLayoutSize(), dynamic_cast<const SwFlyFrameFormat*>( &rFrameFormat ), nAlign);
6393                     m_bPostponedProcessingFly = false ;
6394                 }
6395             }
6396             break;
6397         case ww8::Frame::eFormControl:
6398             {
6399                 const SdrObject* pObject = rFrame.GetFrameFormat().FindRealSdrObject();
6400                 if(ExportAsActiveXControl(pObject))
6401                     m_aPostponedActiveXControls.emplace_back(pObject, &(rFrame.GetFrameFormat()));
6402                 else
6403                     m_aPostponedFormControls.push_back(pObject);
6404                 m_bPostponedProcessingFly = true ;
6405             }
6406             break;
6407         default:
6408             SAL_INFO("sw.ww8", "TODO DocxAttributeOutput::OutputFlyFrame_Impl( const ww8::Frame& rFrame ) - frame type " <<
6409                     ( rFrame.GetWriterType() == ww8::Frame::eTextBox ? "eTextBox":
6410                       ( rFrame.GetWriterType() == ww8::Frame::eOle ? "eOle": "???" ) ) );
6411             break;
6412     }
6413 
6414     m_pSerializer->mergeTopMarks(Tag_OutputFlyFrame);
6415 }
6416 
OutputFlyFrame_Impl(const ww8::Frame & rFrame,const Point &)6417 void DocxAttributeOutput::OutputFlyFrame_Impl(const ww8::Frame& rFrame, const Point& /*rNdTopLeft*/)
6418 {
6419     /// The old OutputFlyFrame_Impl() moved to WriteFlyFrame().
6420     /// Now if a frame anchored inside another frame, it will
6421     /// not be exported immediately, because OOXML does not
6422     /// support that feature, instead it postponed and exported
6423     /// later when the original shape closed.
6424 
6425     if (rFrame.IsInline())
6426     {
6427         m_nEmbedFlyLevel++;
6428         WriteFlyFrame(rFrame);
6429         m_nEmbedFlyLevel--;
6430         return;
6431     }
6432 
6433     if (m_nEmbedFlyLevel == 0)
6434     {
6435         if (m_vPostponedFlys.empty())
6436         {
6437             m_nEmbedFlyLevel++;
6438             WriteFlyFrame(rFrame);
6439             m_nEmbedFlyLevel--;
6440         }
6441         else
6442             for (auto it = m_vPostponedFlys.begin(); it != m_vPostponedFlys.end();)
6443             {
6444                 m_nEmbedFlyLevel++;
6445                 WriteFlyFrame(*it);
6446                 it = m_vPostponedFlys.erase(it);
6447                 m_nEmbedFlyLevel--;
6448             }
6449     }
6450     else
6451     {
6452         bool bFound = false;
6453         for (const auto& i : m_vPostponedFlys)
6454         {
6455             if (i.RefersToSameFrameAs(rFrame))
6456             {
6457                 bFound = true;
6458                 break;
6459             }
6460         }
6461         if (!bFound)
6462         {
6463             if (auto pParentFly = rFrame.GetContentNode()->GetFlyFormat())
6464             {
6465                 auto aHori(rFrame.GetFrameFormat().GetHoriOrient());
6466                 aHori.SetPos(aHori.GetPos() + pParentFly->GetHoriOrient().GetPos());
6467                 auto aVori(rFrame.GetFrameFormat().GetVertOrient());
6468                 aVori.SetPos(aVori.GetPos() + pParentFly->GetVertOrient().GetPos());
6469 
6470                 const_cast<SwFrameFormat&>(rFrame.GetFrameFormat()).SetFormatAttr(aHori);
6471                 const_cast<SwFrameFormat&>(rFrame.GetFrameFormat()).SetFormatAttr(aVori);
6472                 const_cast<SwFrameFormat&>(rFrame.GetFrameFormat()).SetFormatAttr(pParentFly->GetAnchor());
6473 
6474                 m_vPostponedFlys.push_back(rFrame);
6475             }
6476 
6477         }
6478     }
6479 }
6480 
WriteOutliner(const OutlinerParaObject & rParaObj)6481 void DocxAttributeOutput::WriteOutliner(const OutlinerParaObject& rParaObj)
6482 {
6483     const EditTextObject& rEditObj = rParaObj.GetTextObject();
6484     MSWord_SdrAttrIter aAttrIter( m_rExport, rEditObj, TXT_HFTXTBOX );
6485 
6486     sal_Int32 nPara = rEditObj.GetParagraphCount();
6487 
6488     m_pSerializer->startElementNS(XML_w, XML_txbxContent);
6489     for (sal_Int32 n = 0; n < nPara; ++n)
6490     {
6491         if( n )
6492             aAttrIter.NextPara( n );
6493 
6494         OUString aStr( rEditObj.GetText( n ));
6495         sal_Int32 nCurrentPos = 0;
6496         sal_Int32 nEnd = aStr.getLength();
6497 
6498         StartParagraph(ww8::WW8TableNodeInfo::Pointer_t(), false);
6499 
6500         // Write paragraph properties.
6501         StartParagraphProperties();
6502         aAttrIter.OutParaAttr(false);
6503         SfxItemSet aParagraphMarkerProperties(m_rExport.m_rDoc.GetAttrPool());
6504         EndParagraphProperties(aParagraphMarkerProperties, nullptr, nullptr, nullptr);
6505 
6506         do {
6507             const sal_Int32 nNextAttr = std::min(aAttrIter.WhereNext(), nEnd);
6508 
6509             m_pSerializer->startElementNS(XML_w, XML_r);
6510 
6511             // Write run properties.
6512             m_pSerializer->startElementNS(XML_w, XML_rPr);
6513             aAttrIter.OutAttr(nCurrentPos);
6514             WriteCollectedRunProperties();
6515             m_pSerializer->endElementNS(XML_w, XML_rPr);
6516 
6517             bool bTextAtr = aAttrIter.IsTextAttr( nCurrentPos );
6518             if( !bTextAtr )
6519             {
6520                 OUString aOut( aStr.copy( nCurrentPos, nNextAttr - nCurrentPos ) );
6521                 RunText(aOut);
6522             }
6523 
6524             if ( !m_sRawText.isEmpty() )
6525             {
6526                 RunText( m_sRawText );
6527                 m_sRawText.clear();
6528             }
6529 
6530             m_pSerializer->endElementNS( XML_w, XML_r );
6531 
6532             nCurrentPos = nNextAttr;
6533             aAttrIter.NextPos();
6534         }
6535         while( nCurrentPos < nEnd );
6536         EndParagraph(ww8::WW8TableNodeInfoInner::Pointer_t());
6537     }
6538     m_pSerializer->endElementNS( XML_w, XML_txbxContent );
6539 }
6540 
pushToTableExportContext(DocxTableExportContext & rContext)6541 void DocxAttributeOutput::pushToTableExportContext(DocxTableExportContext& rContext)
6542 {
6543     rContext.m_pTableInfo = m_rExport.m_pTableInfo;
6544     m_rExport.m_pTableInfo = std::make_shared<ww8::WW8TableInfo>();
6545 
6546     rContext.m_bTableCellOpen = m_tableReference.m_bTableCellOpen;
6547     m_tableReference.m_bTableCellOpen = false;
6548 
6549     rContext.m_nTableDepth = m_tableReference.m_nTableDepth;
6550     m_tableReference.m_nTableDepth = 0;
6551 
6552     rContext.m_bStartedParaSdt = m_aParagraphSdt.m_bStartedSdt;
6553     m_aParagraphSdt.m_bStartedSdt = false;
6554     rContext.m_bStartedRunSdt = m_aRunSdt.m_bStartedSdt;
6555     m_aRunSdt.m_bStartedSdt = false;
6556 
6557     rContext.m_nHyperLinkCount = m_nHyperLinkCount.back();
6558     m_nHyperLinkCount.back() = 0;
6559 }
6560 
popFromTableExportContext(DocxTableExportContext const & rContext)6561 void DocxAttributeOutput::popFromTableExportContext(DocxTableExportContext const & rContext)
6562 {
6563     m_rExport.m_pTableInfo = rContext.m_pTableInfo;
6564     m_tableReference.m_bTableCellOpen = rContext.m_bTableCellOpen;
6565     m_tableReference.m_nTableDepth = rContext.m_nTableDepth;
6566     m_aParagraphSdt.m_bStartedSdt = rContext.m_bStartedParaSdt;
6567     m_aRunSdt.m_bStartedSdt = rContext.m_bStartedRunSdt;
6568     m_nHyperLinkCount.back() = rContext.m_nHyperLinkCount;
6569 }
6570 
WriteTextBox(uno::Reference<drawing::XShape> xShape)6571 void DocxAttributeOutput::WriteTextBox(uno::Reference<drawing::XShape> xShape)
6572 {
6573     DocxTableExportContext aTableExportContext(*this);
6574 
6575     SwFrameFormat* pTextBox = SwTextBoxHelper::getOtherTextBoxFormat(xShape);
6576     assert(pTextBox);
6577     const SwPosition* pAnchor = nullptr;
6578     const bool bFlyAtPage = pTextBox->GetAnchor().GetAnchorId() == RndStdIds::FLY_AT_PAGE;
6579     if (bFlyAtPage) //tdf135711
6580     {
6581         auto pNdIdx = pTextBox->GetContent().GetContentIdx();
6582         if (pNdIdx) //Is that possible it is null?
6583             pAnchor = new SwPosition(*pNdIdx);
6584     }
6585     else
6586     {
6587         pAnchor = pTextBox->GetAnchor().GetContentAnchor();//This might be null
6588     }
6589 
6590     if (pAnchor) //pAnchor can be null, so that's why not assert here.
6591     {
6592         ww8::Frame aFrame(*pTextBox, *pAnchor);
6593         m_rExport.SdrExporter().writeDMLTextFrame(&aFrame, m_anchorId++, /*bTextBoxOnly=*/true);
6594         if (bFlyAtPage)
6595         {
6596             delete pAnchor;
6597         }
6598     }
6599 }
6600 
WriteVMLTextBox(uno::Reference<drawing::XShape> xShape)6601 void DocxAttributeOutput::WriteVMLTextBox(uno::Reference<drawing::XShape> xShape)
6602 {
6603     DocxTableExportContext aTableExportContext(*this);
6604 
6605     SwFrameFormat* pTextBox = SwTextBoxHelper::getOtherTextBoxFormat(xShape);
6606     assert(pTextBox);
6607     const SwPosition* pAnchor = nullptr;
6608     if (pTextBox->GetAnchor().GetAnchorId() == RndStdIds::FLY_AT_PAGE) //tdf135711
6609     {
6610         auto pNdIdx = pTextBox->GetContent().GetContentIdx();
6611         if (pNdIdx) //Is that possible it is null?
6612             pAnchor = new SwPosition(*pNdIdx);
6613     }
6614     else
6615     {
6616         pAnchor = pTextBox->GetAnchor().GetContentAnchor();//This might be null
6617     }
6618 
6619     if (pAnchor) //pAnchor can be null, so that's why not assert here.
6620     {
6621         ww8::Frame aFrame(*pTextBox, *pAnchor);
6622         m_rExport.SdrExporter().writeVMLTextFrame(&aFrame, /*bTextBoxOnly=*/true);
6623         if (pTextBox->GetAnchor().GetAnchorId() == RndStdIds::FLY_AT_PAGE)
6624         {
6625             delete pAnchor;
6626         }
6627     }
6628 }
6629 
GetDrawingML()6630 oox::drawingml::DrawingML& DocxAttributeOutput::GetDrawingML()
6631 {
6632     return m_rDrawingML;
6633 }
6634 
MaybeOutputBrushItem(SfxItemSet const & rSet)6635 bool DocxAttributeOutput::MaybeOutputBrushItem(SfxItemSet const& rSet)
6636 {
6637     const XFillStyleItem* pXFillStyleItem(rSet.GetItem<XFillStyleItem>(XATTR_FILLSTYLE));
6638 
6639     if ((pXFillStyleItem && pXFillStyleItem->GetValue() != drawing::FillStyle_NONE)
6640         || !m_rExport.SdrExporter().getDMLTextFrameSyntax())
6641     {
6642         return false;
6643     }
6644 
6645     // sw text frames are opaque by default, even with fill none!
6646     std::unique_ptr<SfxItemSet> const pClone(rSet.Clone());
6647     XFillColorItem const aColor(OUString(), COL_WHITE);
6648     pClone->Put(aColor);
6649     // call getSvxBrushItemForSolid - this also takes XFillTransparenceItem into account
6650     XFillStyleItem const aSolid(drawing::FillStyle_SOLID);
6651     pClone->Put(aSolid);
6652     std::unique_ptr<SvxBrushItem> const pBrush(getSvxBrushItemFromSourceSet(*pClone, RES_BACKGROUND));
6653     FormatBackground(*pBrush);
6654     return true;
6655 }
6656 
6657 namespace {
6658 
6659 /// Functor to do case-insensitive ordering of OUString instances.
6660 struct OUStringIgnoreCase
6661 {
operator ()__anon55f3e7cc0a11::OUStringIgnoreCase6662     bool operator() (std::u16string_view lhs, std::u16string_view rhs) const
6663     {
6664         return o3tl::compareToIgnoreAsciiCase(lhs, rhs) < 0;
6665     }
6666 };
6667 
6668 }
6669 
6670 /// Guesses if a style created in Writer (no grab-bag) should be qFormat or not.
lcl_guessQFormat(const OUString & rName,sal_uInt16 nWwId)6671 static bool lcl_guessQFormat(const OUString& rName, sal_uInt16 nWwId)
6672 {
6673     // If the style has no dedicated STI number, then it's probably a custom style -> qFormat.
6674     if (nWwId == ww::stiUser)
6675         return true;
6676 
6677     // Allow exported built-in styles UI language neutral
6678     if ( nWwId == ww::stiNormal ||
6679         ( nWwId>= ww::stiLev1 && nWwId <= ww::stiLev9 ) ||
6680             nWwId == ww::stiCaption || nWwId == ww::stiTitle ||
6681             nWwId == ww::stiSubtitle || nWwId == ww::stiStrong ||
6682             nWwId == ww::stiEmphasis )
6683         return true;
6684 
6685     static o3tl::sorted_vector<OUString, OUStringIgnoreCase> const aAllowlist
6686     {
6687         u"No Spacing"_ustr,
6688         u"List Paragraph"_ustr,
6689         u"Quote"_ustr,
6690         u"Intense Quote"_ustr,
6691         u"Subtle Emphasis"_ustr,
6692         u"Intense Emphasis"_ustr,
6693         u"Subtle Reference"_ustr,
6694         u"Intense Reference"_ustr,
6695         u"Book Title"_ustr,
6696         u"TOC Heading"_ustr,
6697     };
6698     // Not custom style? Then we have a list of standard styles which should be qFormat.
6699     return aAllowlist.find(rName) != aAllowlist.end();
6700 }
6701 
StartStyle(const OUString & rName,StyleType eType,sal_uInt16 nBase,sal_uInt16 nNext,sal_uInt16 nLink,sal_uInt16 nWwId,sal_uInt16 nSlot,bool bAutoUpdate)6702 void DocxAttributeOutput::StartStyle( const OUString& rName, StyleType eType,
6703         sal_uInt16 nBase, sal_uInt16 nNext, sal_uInt16 nLink, sal_uInt16 nWwId, sal_uInt16 nSlot, bool bAutoUpdate )
6704 {
6705     bool bQFormat = false, bUnhideWhenUsed = false, bSemiHidden = false, bLocked = false, bDefault = false, bCustomStyle = false;
6706     OUString aRsid, aUiPriority;
6707     rtl::Reference<FastAttributeList> pStyleAttributeList = FastSerializerHelper::createAttrList();
6708     uno::Any aAny;
6709     if (eType == STYLE_TYPE_PARA || eType == STYLE_TYPE_CHAR)
6710     {
6711         const SwFormat* pFormat = m_rExport.m_pStyles->GetSwFormat(nSlot);
6712         pFormat->GetGrabBagItem(aAny);
6713     }
6714     else
6715     {
6716         const SwNumRule* pRule = m_rExport.m_pStyles->GetSwNumRule(nSlot);
6717         pRule->GetGrabBagItem(aAny);
6718     }
6719     const uno::Sequence<beans::PropertyValue>& rGrabBag = aAny.get< uno::Sequence<beans::PropertyValue> >();
6720 
6721     for (const auto& rProp : rGrabBag)
6722     {
6723         if (rProp.Name == "uiPriority")
6724             aUiPriority = rProp.Value.get<OUString>();
6725         else if (rProp.Name == "qFormat")
6726             bQFormat = true;
6727         else if (rProp.Name == "rsid")
6728             aRsid = rProp.Value.get<OUString>();
6729         else if (rProp.Name == "unhideWhenUsed")
6730             bUnhideWhenUsed = true;
6731         else if (rProp.Name == "semiHidden")
6732             bSemiHidden = true;
6733         else if (rProp.Name == "locked")
6734             bLocked = true;
6735         else if (rProp.Name == "default")
6736             bDefault = rProp.Value.get<bool>();
6737         else if (rProp.Name == "customStyle")
6738             bCustomStyle = rProp.Value.get<bool>();
6739         else
6740             SAL_WARN("sw.ww8", "Unhandled style property: " << rProp.Name);
6741     }
6742 
6743     const char* pType = nullptr;
6744     switch (eType)
6745     {
6746         case STYLE_TYPE_PARA:
6747             pType = "paragraph";
6748             break;
6749         case STYLE_TYPE_CHAR:
6750             pType = "character";
6751             break;
6752         case STYLE_TYPE_LIST: pType = "numbering"; break;
6753     }
6754     pStyleAttributeList->add(FSNS( XML_w, XML_type ), pType);
6755     pStyleAttributeList->add(FSNS(XML_w, XML_styleId), m_rExport.m_pStyles->GetStyleId(nSlot));
6756     if (bDefault)
6757         pStyleAttributeList->add(FSNS(XML_w, XML_default), "1");
6758     if (bCustomStyle)
6759         pStyleAttributeList->add(FSNS(XML_w, XML_customStyle), "1");
6760     m_pSerializer->startElementNS( XML_w, XML_style, pStyleAttributeList);
6761     m_pSerializer->singleElementNS(XML_w, XML_name, FSNS(XML_w, XML_val), rName);
6762 
6763     if ( nBase != 0x0FFF && eType != STYLE_TYPE_LIST)
6764     {
6765         m_pSerializer->singleElementNS( XML_w, XML_basedOn,
6766                 FSNS( XML_w, XML_val ), m_rExport.m_pStyles->GetStyleId(nBase) );
6767     }
6768 
6769     if ( nNext != nSlot && eType != STYLE_TYPE_LIST)
6770     {
6771         m_pSerializer->singleElementNS( XML_w, XML_next,
6772                 FSNS( XML_w, XML_val ), m_rExport.m_pStyles->GetStyleId(nNext) );
6773     }
6774 
6775     if (nLink != 0x0FFF && (eType == STYLE_TYPE_PARA || eType == STYLE_TYPE_CHAR))
6776     {
6777         m_pSerializer->singleElementNS(XML_w, XML_link, FSNS(XML_w, XML_val),
6778                                        m_rExport.m_pStyles->GetStyleId(nLink));
6779     }
6780 
6781     if ( bAutoUpdate )
6782         m_pSerializer->singleElementNS(XML_w, XML_autoRedefine);
6783 
6784     if (!aUiPriority.isEmpty())
6785         m_pSerializer->singleElementNS(XML_w, XML_uiPriority, FSNS(XML_w, XML_val), aUiPriority);
6786     if (bSemiHidden)
6787         m_pSerializer->singleElementNS(XML_w, XML_semiHidden);
6788     if (bUnhideWhenUsed)
6789         m_pSerializer->singleElementNS(XML_w, XML_unhideWhenUsed);
6790 
6791     if (bQFormat || lcl_guessQFormat(rName, nWwId))
6792         m_pSerializer->singleElementNS(XML_w, XML_qFormat);
6793     if (bLocked)
6794         m_pSerializer->singleElementNS(XML_w, XML_locked);
6795     if (!aRsid.isEmpty())
6796         m_pSerializer->singleElementNS(XML_w, XML_rsid, FSNS(XML_w, XML_val), aRsid);
6797 }
6798 
EndStyle()6799 void DocxAttributeOutput::EndStyle()
6800 {
6801     m_pSerializer->endElementNS( XML_w, XML_style );
6802 }
6803 
StartStyleProperties(bool bParProp,sal_uInt16)6804 void DocxAttributeOutput::StartStyleProperties( bool bParProp, sal_uInt16 /*nStyle*/ )
6805 {
6806     if ( bParProp )
6807     {
6808         m_pSerializer->startElementNS(XML_w, XML_pPr);
6809         InitCollectedParagraphProperties();
6810     }
6811     else
6812     {
6813         m_pSerializer->startElementNS(XML_w, XML_rPr);
6814         InitCollectedRunProperties();
6815     }
6816 }
6817 
EndStyleProperties(bool bParProp)6818 void DocxAttributeOutput::EndStyleProperties( bool bParProp )
6819 {
6820     if ( bParProp )
6821     {
6822         WriteCollectedParagraphProperties();
6823 
6824         // Merge the marks for the ordered elements
6825         m_pSerializer->mergeTopMarks(Tag_InitCollectedParagraphProperties);
6826 
6827         m_pSerializer->endElementNS( XML_w, XML_pPr );
6828     }
6829     else
6830     {
6831         WriteCollectedRunProperties();
6832 
6833         // Merge the marks for the ordered elements
6834         m_pSerializer->mergeTopMarks(Tag_InitCollectedRunProperties);
6835 
6836         m_pSerializer->endElementNS( XML_w, XML_rPr );
6837     }
6838 }
6839 
OutlineNumbering(sal_uInt8 const)6840 void DocxAttributeOutput::OutlineNumbering(sal_uInt8 const /*nLvl*/)
6841 {
6842     // Handled by ParaOutlineLevel() instead.
6843 }
6844 
ParaOutlineLevel(const SfxUInt16Item & rItem)6845 void DocxAttributeOutput::ParaOutlineLevel(const SfxUInt16Item& rItem)
6846 {
6847     sal_uInt16 nOutLvl = std::min(rItem.GetValue(), sal_uInt16(WW8ListManager::nMaxLevel));
6848     // Outline Level: in LO Body Text = 0, in MS Body Text = 9
6849     nOutLvl = nOutLvl ? nOutLvl - 1 : 9;
6850     m_pSerializer->singleElementNS(XML_w, XML_outlineLvl, FSNS(XML_w, XML_val), OString::number(nOutLvl));
6851 }
6852 
PageBreakBefore(bool bBreak)6853 void DocxAttributeOutput::PageBreakBefore( bool bBreak )
6854 {
6855     if ( bBreak )
6856         m_pSerializer->singleElementNS(XML_w, XML_pageBreakBefore);
6857     else
6858         m_pSerializer->singleElementNS( XML_w, XML_pageBreakBefore,
6859                 FSNS( XML_w, XML_val ), "false" );
6860 }
6861 
SectionBreak(sal_uInt8 nC,bool bBreakAfter,const WW8_SepInfo * pSectionInfo,bool bExtraPageBreak)6862 void DocxAttributeOutput::SectionBreak( sal_uInt8 nC, bool bBreakAfter, const WW8_SepInfo* pSectionInfo, bool bExtraPageBreak)
6863 {
6864     switch ( nC )
6865     {
6866         case msword::ColumnBreak:
6867             // The column break should be output in the next paragraph...
6868             if ( m_nColBreakStatus == COLBRK_WRITE )
6869                 m_nColBreakStatus = COLBRK_WRITEANDPOSTPONE;
6870             else
6871                 m_nColBreakStatus = COLBRK_POSTPONE;
6872             break;
6873         case msword::PageBreak:
6874             if ( pSectionInfo )
6875             {
6876                 // Detect when the current node is the last node in the
6877                 // document: the last section is written explicitly in
6878                 // DocxExport::WriteMainText(), don't duplicate that here.
6879                 SwNodeIndex aCurrentNode(m_rExport.m_pCurPam->GetPointNode());
6880                 SwNodeIndex aLastNode(m_rExport.m_rDoc.GetNodes().GetEndOfContent(), -1);
6881                 bool bEmit = aCurrentNode != aLastNode;
6882 
6883                 if (!bEmit)
6884                 {
6885                     // Need to still emit an empty section at the end of the
6886                     // document in case balanced columns are wanted, since the last
6887                     // section in Word is always balanced.
6888                     sal_uInt16 nColumns = 1;
6889                     bool bBalance = false;
6890                     if (const SwSectionFormat* pFormat = pSectionInfo->pSectionFormat)
6891                     {
6892                         if (pFormat != reinterpret_cast<SwSectionFormat*>(sal_IntPtr(-1)))
6893                         {
6894                             nColumns = pFormat->GetCol().GetNumCols();
6895                             const SwFormatNoBalancedColumns& rNoBalanced = pFormat->GetBalancedColumns();
6896                             bBalance = !rNoBalanced.GetValue();
6897                         }
6898                     }
6899                     bEmit = (nColumns > 1 && bBalance);
6900                 }
6901 
6902                 // don't add section properties if this will be the first
6903                 // paragraph in the document
6904                 if ( !m_bParagraphOpened && !m_bIsFirstParagraph && bEmit )
6905                 {
6906                     // Create a dummy paragraph if needed
6907                     m_pSerializer->startElementNS(XML_w, XML_p);
6908                     m_pSerializer->startElementNS(XML_w, XML_pPr);
6909 
6910                     m_rExport.SectionProperties( *pSectionInfo );
6911 
6912                     m_pSerializer->endElementNS( XML_w, XML_pPr );
6913                     if (bExtraPageBreak)
6914                     {
6915                         m_pSerializer->startElementNS(XML_w, XML_r);
6916                         m_pSerializer->singleElementNS(XML_w, XML_br, FSNS(XML_w, XML_type), "page");
6917                         m_pSerializer->endElementNS(XML_w, XML_r);
6918                     }
6919                     m_pSerializer->endElementNS( XML_w, XML_p );
6920                 }
6921                 else
6922                 {
6923                     if (bExtraPageBreak && m_bParagraphOpened)
6924                     {
6925                         m_pSerializer->startElementNS(XML_w, XML_r);
6926                         m_pSerializer->singleElementNS(XML_w, XML_br, FSNS(XML_w, XML_type), "page");
6927                         m_pSerializer->endElementNS(XML_w, XML_r);
6928                     }
6929                     // postpone the output of this; it has to be done inside the
6930                     // paragraph properties, so remember it until then
6931                     m_pSectionInfo.reset( new WW8_SepInfo( *pSectionInfo ));
6932                 }
6933             }
6934             else if ( m_bParagraphOpened )
6935             {
6936                 if (bBreakAfter)
6937                     // tdf#128889
6938                     m_bPageBreakAfter = true;
6939                 else
6940                 {
6941                     m_pSerializer->startElementNS(XML_w, XML_r);
6942                     m_pSerializer->singleElementNS(XML_w, XML_br, FSNS(XML_w, XML_type), "page");
6943                     m_pSerializer->endElementNS(XML_w, XML_r);
6944                 }
6945             }
6946             else
6947                 m_bPostponedPageBreak = true;
6948 
6949             break;
6950         default:
6951             SAL_INFO("sw.ww8", "Unknown section break to write: " << nC );
6952             break;
6953     }
6954 }
6955 
EndParaSdtBlock()6956 void DocxAttributeOutput::EndParaSdtBlock()
6957 {
6958     if (m_aParagraphSdt.m_bStartedSdt)
6959     {
6960         // Paragraph-level SDT still open? Close it now.
6961         m_aParagraphSdt.EndSdtBlock(m_pSerializer);
6962     }
6963 }
6964 
StartSection()6965 void DocxAttributeOutput::StartSection()
6966 {
6967     m_pSerializer->startElementNS(XML_w, XML_sectPr);
6968     m_bOpenedSectPr = true;
6969 
6970     // Write the elements in the spec order
6971     static const sal_Int32 aOrder[] =
6972     {
6973         FSNS( XML_w, XML_headerReference ),
6974         FSNS( XML_w, XML_footerReference ),
6975         FSNS( XML_w, XML_footnotePr ),
6976         FSNS( XML_w, XML_endnotePr ),
6977         FSNS( XML_w, XML_type ),
6978         FSNS( XML_w, XML_pgSz ),
6979         FSNS( XML_w, XML_pgMar ),
6980         FSNS( XML_w, XML_paperSrc ),
6981         FSNS( XML_w, XML_pgBorders ),
6982         FSNS( XML_w, XML_lnNumType ),
6983         FSNS( XML_w, XML_pgNumType ),
6984         FSNS( XML_w, XML_cols ),
6985         FSNS( XML_w, XML_formProt ),
6986         FSNS( XML_w, XML_vAlign ),
6987         FSNS( XML_w, XML_noEndnote ),
6988         FSNS( XML_w, XML_titlePg ),
6989         FSNS( XML_w, XML_textDirection ),
6990         FSNS( XML_w, XML_bidi ),
6991         FSNS( XML_w, XML_rtlGutter ),
6992         FSNS( XML_w, XML_docGrid ),
6993         FSNS( XML_w, XML_printerSettings ),
6994         FSNS( XML_w, XML_sectPrChange )
6995     };
6996 
6997     // postpone the output so that we can later [in EndParagraphProperties()]
6998     // prepend the properties before the run
6999     // coverity[overrun-buffer-arg : FALSE] - coverity has difficulty with css::uno::Sequence
7000     m_pSerializer->mark(Tag_StartSection, comphelper::containerToSequence(aOrder));
7001     m_bHadSectPr = true;
7002 }
7003 
EndSection()7004 void DocxAttributeOutput::EndSection()
7005 {
7006     // Write the section properties
7007     if ( m_pSectionSpacingAttrList.is() )
7008     {
7009         m_pSerializer->singleElementNS( XML_w, XML_pgMar, detachFrom( m_pSectionSpacingAttrList ) );
7010     }
7011 
7012     // Order the elements
7013     m_pSerializer->mergeTopMarks(Tag_StartSection);
7014 
7015     m_pSerializer->endElementNS( XML_w, XML_sectPr );
7016     m_bOpenedSectPr = false;
7017 }
7018 
SectionFormProtection(bool bProtected)7019 void DocxAttributeOutput::SectionFormProtection( bool bProtected )
7020 {
7021     if ( bProtected )
7022         m_pSerializer->singleElementNS(XML_w, XML_formProt, FSNS(XML_w, XML_val), "true");
7023     else
7024         m_pSerializer->singleElementNS(XML_w, XML_formProt, FSNS(XML_w, XML_val), "false");
7025 }
7026 
SectionRtlGutter(const SfxBoolItem & rRtlGutter)7027 void DocxAttributeOutput::SectionRtlGutter(const SfxBoolItem& rRtlGutter)
7028 {
7029     if (!rRtlGutter.GetValue())
7030     {
7031         return;
7032     }
7033 
7034     m_pSerializer->singleElementNS(XML_w, XML_rtlGutter);
7035 }
7036 
TextLineBreak(const SwFormatLineBreak & rLineBreak)7037 void DocxAttributeOutput::TextLineBreak(const SwFormatLineBreak& rLineBreak)
7038 {
7039     m_oLineBreakClear = rLineBreak.GetValue();
7040 }
7041 
WriteLineBreak()7042 void DocxAttributeOutput::WriteLineBreak()
7043 {
7044     if (!m_oLineBreakClear.has_value())
7045     {
7046         return;
7047     }
7048 
7049     rtl::Reference<FastAttributeList> pAttr = FastSerializerHelper::createAttrList();
7050     pAttr->add(FSNS(XML_w, XML_type), "textWrapping");
7051     switch (*m_oLineBreakClear)
7052     {
7053         case SwLineBreakClear::NONE:
7054             pAttr->add(FSNS(XML_w, XML_clear), "none");
7055             break;
7056         case SwLineBreakClear::LEFT:
7057             pAttr->add(FSNS(XML_w, XML_clear), "left");
7058             break;
7059         case SwLineBreakClear::RIGHT:
7060             pAttr->add(FSNS(XML_w, XML_clear), "right");
7061             break;
7062         case SwLineBreakClear::ALL:
7063             pAttr->add(FSNS(XML_w, XML_clear), "all");
7064             break;
7065     }
7066     m_oLineBreakClear.reset();
7067 
7068     m_pSerializer->singleElementNS(XML_w, XML_br, pAttr);
7069 }
7070 
SectionLineNumbering(sal_uLong nRestartNo,const SwLineNumberInfo & rLnNumInfo)7071 void DocxAttributeOutput::SectionLineNumbering( sal_uLong nRestartNo, const SwLineNumberInfo& rLnNumInfo )
7072 {
7073     rtl::Reference<FastAttributeList> pAttr = FastSerializerHelper::createAttrList();
7074     pAttr->add( FSNS( XML_w, XML_countBy ), OString::number(rLnNumInfo.GetCountBy()));
7075     pAttr->add( FSNS( XML_w, XML_restart ), rLnNumInfo.IsRestartEachPage() ? "newPage" : "continuous" );
7076     if( rLnNumInfo.GetPosFromLeft())
7077         pAttr->add( FSNS( XML_w, XML_distance ), OString::number(rLnNumInfo.GetPosFromLeft()));
7078     if (nRestartNo > 0)
7079         // Writer is 1-based, Word is 0-based.
7080         pAttr->add(FSNS(XML_w, XML_start), OString::number(nRestartNo - 1));
7081     m_pSerializer->singleElementNS( XML_w, XML_lnNumType, pAttr );
7082 }
7083 
SectionTitlePage()7084 void DocxAttributeOutput::SectionTitlePage()
7085 {
7086     m_pSerializer->singleElementNS(XML_w, XML_titlePg);
7087 }
7088 
SectionPageBorders(const SwFrameFormat * pFormat,const SwFrameFormat *)7089 void DocxAttributeOutput::SectionPageBorders( const SwFrameFormat* pFormat, const SwFrameFormat* /*pFirstPageFormat*/ )
7090 {
7091     // Output the margins
7092 
7093     const SvxBoxItem& rBox = pFormat->GetBox( );
7094 
7095     const SvxBorderLine* pLeft = rBox.GetLeft( );
7096     const SvxBorderLine* pTop = rBox.GetTop( );
7097     const SvxBorderLine* pRight = rBox.GetRight( );
7098     const SvxBorderLine* pBottom = rBox.GetBottom( );
7099 
7100     if ( !(pBottom || pTop || pLeft || pRight) )
7101         return;
7102 
7103     OutputBorderOptions aOutputBorderOptions = lcl_getBoxBorderOptions();
7104 
7105     // Check if there is a shadow item
7106     const SfxPoolItem* pItem = GetExport().HasItem( RES_SHADOW );
7107     if ( pItem )
7108     {
7109         const SvxShadowItem* pShadowItem = static_cast<const SvxShadowItem*>(pItem);
7110         aOutputBorderOptions.aShadowLocation = pShadowItem->GetLocation();
7111     }
7112 
7113     // By top margin, impl_borders() means the distance between the top of the page and the header frame.
7114     editeng::WordPageMargins aMargins = m_pageMargins;
7115     HdFtDistanceGlue aGlue(pFormat->GetAttrSet());
7116     if (aGlue.HasHeader())
7117         aMargins.nTop = aGlue.m_DyaHdrTop;
7118     // Ditto for bottom margin.
7119     if (aGlue.HasFooter())
7120         aMargins.nBottom = aGlue.m_DyaHdrBottom;
7121 
7122     if (pFormat->GetDoc()->getIDocumentSettingAccess().get(DocumentSettingId::GUTTER_AT_TOP))
7123     {
7124         aMargins.nTop += pFormat->GetLRSpace().GetGutterMargin();
7125     }
7126     else
7127     {
7128         aMargins.nLeft += pFormat->GetLRSpace().GetGutterMargin();
7129     }
7130 
7131     aOutputBorderOptions.pDistances = std::make_shared<editeng::WordBorderDistances>();
7132     editeng::BorderDistancesToWord(rBox, aMargins, *aOutputBorderOptions.pDistances);
7133 
7134     // All distances are relative to the text margins
7135     m_pSerializer->startElementNS(XML_w, XML_pgBorders,
7136         FSNS(XML_w, XML_display), "allPages",
7137         FSNS(XML_w, XML_offsetFrom), aOutputBorderOptions.pDistances->bFromEdge ? "page" : "text");
7138 
7139     std::map<SvxBoxItemLine, css::table::BorderLine2> aEmptyMap; // empty styles map
7140     impl_borders( m_pSerializer, rBox, aOutputBorderOptions, aEmptyMap );
7141 
7142     m_pSerializer->endElementNS( XML_w, XML_pgBorders );
7143 
7144 }
7145 
SectionBiDi(bool bBiDi)7146 void DocxAttributeOutput::SectionBiDi( bool bBiDi )
7147 {
7148     if ( bBiDi )
7149         m_pSerializer->singleElementNS(XML_w, XML_bidi);
7150 }
7151 
7152 // Converting Numbering Format Code to string
lcl_ConvertNumberingType(sal_Int16 nNumberingType,const SfxItemSet * pOutSet,OString & rFormat,const OString & sDefault=""_ostr)7153 static OString lcl_ConvertNumberingType(sal_Int16 nNumberingType, const SfxItemSet* pOutSet, OString& rFormat, const OString& sDefault = ""_ostr )
7154 {
7155     OString aType = sDefault;
7156 
7157     switch ( nNumberingType )
7158     {
7159         case SVX_NUM_CHARS_UPPER_LETTER:
7160         case SVX_NUM_CHARS_UPPER_LETTER_N:  aType = "upperLetter"_ostr; break;
7161 
7162         case SVX_NUM_CHARS_LOWER_LETTER:
7163         case SVX_NUM_CHARS_LOWER_LETTER_N:  aType = "lowerLetter"_ostr; break;
7164 
7165         case SVX_NUM_ROMAN_UPPER:           aType = "upperRoman"_ostr;  break;
7166         case SVX_NUM_ROMAN_LOWER:           aType = "lowerRoman"_ostr;  break;
7167         case SVX_NUM_ARABIC:                aType = "decimal"_ostr;     break;
7168 
7169         case SVX_NUM_BITMAP:
7170         case SVX_NUM_CHAR_SPECIAL:          aType = "bullet"_ostr;      break;
7171 
7172         case style::NumberingType::CHARS_HEBREW: aType = "hebrew2"_ostr; break;
7173         case style::NumberingType::NUMBER_HEBREW: aType = "hebrew1"_ostr; break;
7174         case style::NumberingType::NUMBER_NONE: aType = "none"_ostr; break;
7175         case style::NumberingType::FULLWIDTH_ARABIC: aType="decimalFullWidth"_ostr; break;
7176         case style::NumberingType::TIAN_GAN_ZH: aType="ideographTraditional"_ostr; break;
7177         case style::NumberingType::DI_ZI_ZH: aType="ideographZodiac"_ostr; break;
7178         case style::NumberingType::NUMBER_LOWER_ZH:
7179             aType="taiwaneseCountingThousand"_ostr;
7180             if (pOutSet) {
7181                 const SvxLanguageItem& rLang = pOutSet->Get( RES_CHRATR_CJK_LANGUAGE);
7182                 const LanguageType eLang = rLang.GetLanguage();
7183 
7184                 if (LANGUAGE_CHINESE_SIMPLIFIED == eLang) {
7185                     aType="chineseCountingThousand"_ostr;
7186                 }
7187             }
7188         break;
7189         case style::NumberingType::NUMBER_UPPER_ZH_TW: aType="ideographLegalTraditional"_ostr;break;
7190         case style::NumberingType::NUMBER_UPPER_ZH: aType="chineseLegalSimplified"_ostr; break;
7191         case style::NumberingType::NUMBER_TRADITIONAL_JA: aType="japaneseLegal"_ostr;break;
7192         case style::NumberingType::AIU_FULLWIDTH_JA: aType="aiueoFullWidth"_ostr;break;
7193         case style::NumberingType::AIU_HALFWIDTH_JA: aType="aiueo"_ostr;break;
7194         case style::NumberingType::IROHA_FULLWIDTH_JA: aType="iroha"_ostr;break;
7195         case style::NumberingType::IROHA_HALFWIDTH_JA: aType="irohaFullWidth"_ostr;break;
7196         case style::NumberingType::HANGUL_SYLLABLE_KO: aType="ganada"_ostr;break;
7197         case style::NumberingType::HANGUL_JAMO_KO: aType="chosung"_ostr;break;
7198         case style::NumberingType::NUMBER_HANGUL_KO: aType="koreanCounting"_ostr; break;
7199         case style::NumberingType::NUMBER_LEGAL_KO: aType = "koreanLegal"_ostr; break;
7200         case style::NumberingType::NUMBER_DIGITAL_KO: aType = "koreanDigital"_ostr; break;
7201         case style::NumberingType::NUMBER_DIGITAL2_KO: aType = "koreanDigital2"_ostr; break;
7202         case style::NumberingType::CIRCLE_NUMBER: aType="decimalEnclosedCircle"_ostr; break;
7203         case style::NumberingType::CHARS_ARABIC: aType="arabicAlpha"_ostr; break;
7204         case style::NumberingType::CHARS_ARABIC_ABJAD: aType="arabicAbjad"_ostr; break;
7205         case style::NumberingType::CHARS_THAI: aType="thaiLetters"_ostr; break;
7206         case style::NumberingType::CHARS_PERSIAN:
7207         case style::NumberingType::CHARS_NEPALI: aType="hindiVowels"_ostr; break;
7208         case style::NumberingType::CHARS_CYRILLIC_UPPER_LETTER_RU:
7209         case style::NumberingType::CHARS_CYRILLIC_UPPER_LETTER_N_RU: aType = "russianUpper"_ostr; break;
7210         case style::NumberingType::CHARS_CYRILLIC_LOWER_LETTER_RU:
7211         case style::NumberingType::CHARS_CYRILLIC_LOWER_LETTER_N_RU: aType = "russianLower"_ostr; break;
7212         case style::NumberingType::TEXT_NUMBER: aType="ordinal"_ostr; break;
7213         case style::NumberingType::TEXT_CARDINAL: aType="cardinalText"_ostr; break;
7214         case style::NumberingType::TEXT_ORDINAL: aType="ordinalText"_ostr; break;
7215         case style::NumberingType::SYMBOL_CHICAGO: aType="chicago"_ostr; break;
7216         case style::NumberingType::ARABIC_ZERO: aType = "decimalZero"_ostr; break;
7217         case style::NumberingType::ARABIC_ZERO3:
7218             aType = "custom"_ostr;
7219             rFormat = "001, 002, 003, ..."_ostr;
7220             break;
7221         case style::NumberingType::ARABIC_ZERO4:
7222             aType = "custom"_ostr;
7223             rFormat = "0001, 0002, 0003, ..."_ostr;
7224             break;
7225         case style::NumberingType::ARABIC_ZERO5:
7226             aType = "custom"_ostr;
7227             rFormat = "00001, 00002, 00003, ..."_ostr;
7228             break;
7229 /*
7230         Fallback the rest to the suggested default.
7231         case style::NumberingType::NATIVE_NUMBERING:
7232         case style::NumberingType::HANGUL_CIRCLED_JAMO_KO:
7233         case style::NumberingType::HANGUL_CIRCLED_SYLLABLE_KO:
7234         case style::NumberingType::CHARS_GREEK_UPPER_LETTER:
7235         case style::NumberingType::CHARS_GREEK_LOWER_LETTER:
7236         case style::NumberingType::PAGE_DESCRIPTOR:
7237         case style::NumberingType::TRANSLITERATION:
7238         case style::NumberingType::CHARS_KHMER:
7239         case style::NumberingType::CHARS_LAO:
7240         case style::NumberingType::CHARS_TIBETAN:
7241         case style::NumberingType::CHARS_CYRILLIC_UPPER_LETTER_BG:
7242         case style::NumberingType::CHARS_CYRILLIC_LOWER_LETTER_BG:
7243         case style::NumberingType::CHARS_CYRILLIC_UPPER_LETTER_N_BG:
7244         case style::NumberingType::CHARS_CYRILLIC_LOWER_LETTER_N_BG:
7245         case style::NumberingType::CHARS_MYANMAR:
7246         case style::NumberingType::CHARS_CYRILLIC_UPPER_LETTER_SR:
7247         case style::NumberingType::CHARS_CYRILLIC_LOWER_LETTER_SR:
7248         case style::NumberingType::CHARS_CYRILLIC_UPPER_LETTER_N_SR:
7249         case style::NumberingType::CHARS_CYRILLIC_LOWER_LETTER_N_SR:
7250         case style::NumberingType::CHARS_CYRILLIC_UPPER_LETTER_UK:
7251         case style::NumberingType::CHARS_CYRILLIC_LOWER_LETTER_UK:
7252         case style::NumberingType::CHARS_CYRILLIC_UPPER_LETTER_N_UK:
7253         case style::NumberingType::CHARS_CYRILLIC_LOWER_LETTER_N_UK:
7254 */
7255         default: break;
7256     }
7257     return aType;
7258 }
7259 
7260 
SectionPageNumbering(sal_uInt16 nNumType,const::std::optional<sal_uInt16> & oPageRestartNumber)7261 void DocxAttributeOutput::SectionPageNumbering( sal_uInt16 nNumType, const ::std::optional<sal_uInt16>& oPageRestartNumber )
7262 {
7263     // FIXME Not called properly with page styles like "First Page"
7264 
7265     rtl::Reference<FastAttributeList> pAttr = FastSerializerHelper::createAttrList();
7266 
7267     // std::nullopt means no restart: then don't output that attribute if it is negative
7268     if ( oPageRestartNumber )
7269        pAttr->add( FSNS( XML_w, XML_start ), OString::number( *oPageRestartNumber ) );
7270 
7271     // nNumType corresponds to w:fmt. See WW8Export::GetNumId() for more precisions
7272     OString aCustomFormat;
7273     OString aFormat(lcl_ConvertNumberingType(nNumType, nullptr, aCustomFormat));
7274     if (!aFormat.isEmpty() && aCustomFormat.isEmpty())
7275         pAttr->add(FSNS(XML_w, XML_fmt), aFormat);
7276 
7277     m_pSerializer->singleElementNS( XML_w, XML_pgNumType, pAttr );
7278 
7279     // see 2.6.12 pgNumType (Page Numbering Settings)
7280     SAL_INFO("sw.ww8", "TODO DocxAttributeOutput::SectionPageNumbering()" );
7281 }
7282 
SectionType(sal_uInt8 nBreakCode)7283 void DocxAttributeOutput::SectionType( sal_uInt8 nBreakCode )
7284 {
7285     /*  break code:   0 No break, 1 New column
7286         2 New page, 3 Even page, 4 Odd page
7287         */
7288     const char* pType;
7289     switch ( nBreakCode )
7290     {
7291         case 1:  pType = "nextColumn"; break;
7292         case 2:  pType = "nextPage";   break;
7293         case 3:  pType = "evenPage";   break;
7294         case 4:  pType = "oddPage";    break;
7295         default: pType = "continuous"; break;
7296     }
7297 
7298     m_pSerializer->singleElementNS(XML_w, XML_type, FSNS(XML_w, XML_val), pType);
7299 }
7300 
TextVerticalAdjustment(const drawing::TextVerticalAdjust nVA)7301 void DocxAttributeOutput::TextVerticalAdjustment( const drawing::TextVerticalAdjust nVA )
7302 {
7303     switch( nVA )
7304     {
7305         case drawing::TextVerticalAdjust_CENTER:
7306             m_pSerializer->singleElementNS(XML_w, XML_vAlign, FSNS(XML_w, XML_val), "center");
7307             break;
7308         case drawing::TextVerticalAdjust_BOTTOM:
7309             m_pSerializer->singleElementNS(XML_w, XML_vAlign, FSNS(XML_w, XML_val), "bottom");
7310             break;
7311         case drawing::TextVerticalAdjust_BLOCK:  //justify
7312             m_pSerializer->singleElementNS(XML_w, XML_vAlign, FSNS(XML_w, XML_val), "both");
7313             break;
7314         default:
7315             break;
7316     }
7317 }
7318 
StartFont(const OUString & rFamilyName) const7319 void DocxAttributeOutput::StartFont( const OUString& rFamilyName ) const
7320 {
7321     m_pSerializer->startElementNS(XML_w, XML_font, FSNS(XML_w, XML_name), rFamilyName);
7322 }
7323 
EndFont() const7324 void DocxAttributeOutput::EndFont() const
7325 {
7326     m_pSerializer->endElementNS( XML_w, XML_font );
7327 }
7328 
FontAlternateName(const OUString & rName) const7329 void DocxAttributeOutput::FontAlternateName( const OUString& rName ) const
7330 {
7331     m_pSerializer->singleElementNS(XML_w, XML_altName, FSNS(XML_w, XML_val), rName);
7332 }
7333 
FontCharset(sal_uInt8 nCharSet,rtl_TextEncoding nEncoding) const7334 void DocxAttributeOutput::FontCharset( sal_uInt8 nCharSet, rtl_TextEncoding nEncoding ) const
7335 {
7336     rtl::Reference<FastAttributeList> pAttr = FastSerializerHelper::createAttrList();
7337 
7338     OString aCharSet( OString::number( nCharSet, 16 ) );
7339     if ( aCharSet.getLength() == 1 )
7340         aCharSet = "0" + aCharSet;
7341     pAttr->add(FSNS(XML_w, XML_val), aCharSet);
7342 
7343     if (GetExport().GetFilter().getVersion() != oox::core::ECMA_376_1ST_EDITION)
7344     {
7345         if( const char* charset = rtl_getMimeCharsetFromTextEncoding( nEncoding ))
7346             pAttr->add( FSNS( XML_w, XML_characterSet ), charset );
7347     }
7348 
7349     m_pSerializer->singleElementNS( XML_w, XML_charset, pAttr );
7350 }
7351 
FontFamilyType(FontFamily eFamily) const7352 void DocxAttributeOutput::FontFamilyType( FontFamily eFamily ) const
7353 {
7354     const char* pFamily;
7355     switch ( eFamily )
7356     {
7357         case FAMILY_ROMAN:      pFamily = "roman"; break;
7358         case FAMILY_SWISS:      pFamily = "swiss"; break;
7359         case FAMILY_MODERN:     pFamily = "modern"; break;
7360         case FAMILY_SCRIPT:     pFamily = "script"; break;
7361         case FAMILY_DECORATIVE: pFamily = "decorative"; break;
7362         default:                pFamily = "auto"; break; // no font family
7363     }
7364 
7365     m_pSerializer->singleElementNS(XML_w, XML_family, FSNS(XML_w, XML_val), pFamily);
7366 }
7367 
FontPitchType(FontPitch ePitch) const7368 void DocxAttributeOutput::FontPitchType( FontPitch ePitch ) const
7369 {
7370     const char* pPitch;
7371     switch ( ePitch )
7372     {
7373         case PITCH_VARIABLE: pPitch = "variable"; break;
7374         case PITCH_FIXED:    pPitch = "fixed"; break;
7375         default:             pPitch = "default"; break; // no info about the pitch
7376     }
7377 
7378     m_pSerializer->singleElementNS(XML_w, XML_pitch, FSNS(XML_w, XML_val), pPitch);
7379 }
7380 
EmbedFont(std::u16string_view name,FontFamily family,FontPitch pitch)7381 void DocxAttributeOutput::EmbedFont( std::u16string_view name, FontFamily family, FontPitch pitch )
7382 {
7383     if( !m_rExport.m_rDoc.getIDocumentSettingAccess().get( DocumentSettingId::EMBED_FONTS ))
7384         return; // no font embedding with this document
7385     bool foundFont
7386         = EmbedFontStyle(name, XML_embedRegular, family, ITALIC_NONE, WEIGHT_NORMAL, pitch);
7387     foundFont
7388         = EmbedFontStyle(name, XML_embedBold, family, ITALIC_NONE, WEIGHT_BOLD, pitch) || foundFont;
7389     foundFont = EmbedFontStyle(name, XML_embedItalic, family, ITALIC_NORMAL, WEIGHT_NORMAL, pitch)
7390                 || foundFont;
7391     foundFont = EmbedFontStyle(name, XML_embedBoldItalic, family, ITALIC_NORMAL, WEIGHT_BOLD, pitch)
7392                 || foundFont;
7393     if (!foundFont)
7394         EmbedFontStyle(name, XML_embedRegular, family, ITALIC_NONE, WEIGHT_DONTKNOW, pitch);
7395 }
7396 
toHexChar(int value)7397 static char toHexChar( int value )
7398 {
7399     return value >= 10 ? value + 'A' - 10 : value + '0';
7400 }
7401 
EmbedFontStyle(std::u16string_view name,int tag,FontFamily family,FontItalic italic,FontWeight weight,FontPitch pitch)7402 bool DocxAttributeOutput::EmbedFontStyle(std::u16string_view name, int tag, FontFamily family,
7403                                          FontItalic italic, FontWeight weight, FontPitch pitch)
7404 {
7405     // Embed font if at least viewing is allowed (in which case the opening app must check
7406     // the font license rights too and open either read-only or not use the font for editing).
7407     OUString fontUrl = EmbeddedFontsHelper::fontFileUrl( name, family, italic, weight, pitch,
7408         EmbeddedFontsHelper::FontRights::ViewingAllowed );
7409     if( fontUrl.isEmpty())
7410         return false;
7411     // TODO IDocumentSettingAccess::EMBED_SYSTEM_FONTS
7412     if( !m_FontFilesMap.count( fontUrl ))
7413     {
7414         osl::File file( fontUrl );
7415         if( file.open( osl_File_OpenFlag_Read ) != osl::File::E_None )
7416             return false;
7417         uno::Reference< css::io::XOutputStream > xOutStream = m_rExport.GetFilter().openFragmentStream(
7418             "word/fonts/font" + OUString::number(m_nextFontId) + ".odttf",
7419             u"application/vnd.openxmlformats-officedocument.obfuscatedFont"_ustr );
7420         // Not much point in trying hard with the obfuscation key, whoever reads the spec can read the font anyway,
7421         // so just alter the first and last part of the key.
7422         char fontKeyStr[] = "{00014A78-CABC-4EF0-12AC-5CD89AEFDE00}";
7423         sal_uInt8 fontKey[ 16 ] = { 0, 0xDE, 0xEF, 0x9A, 0xD8, 0x5C, 0xAC, 0x12, 0xF0, 0x4E,
7424             0xBC, 0xCA, 0x78, 0x4A, 0x01, 0 };
7425         fontKey[ 0 ] = fontKey[ 15 ] = m_nextFontId % 256;
7426         fontKeyStr[ 1 ] = fontKeyStr[ 35 ] = toHexChar(( m_nextFontId % 256 ) / 16 );
7427         fontKeyStr[ 2 ] = fontKeyStr[ 36 ] = toHexChar(( m_nextFontId % 256 ) % 16 );
7428         unsigned char buffer[ 4096 ];
7429         sal_uInt64 readSize;
7430         file.read( buffer, 32, readSize );
7431         if( readSize < 32 )
7432         {
7433             SAL_WARN( "sw.ww8", "Font file size too small (" << fontUrl << ")" );
7434             xOutStream->closeOutput();
7435             return false;
7436         }
7437         for( int i = 0;
7438              i < 16;
7439              ++i )
7440         {
7441             buffer[ i ] ^= fontKey[ i ];
7442             buffer[ i + 16 ] ^= fontKey[ i ];
7443         }
7444         xOutStream->writeBytes( uno::Sequence< sal_Int8 >( reinterpret_cast< const sal_Int8* >( buffer ), 32 ));
7445         for(;;)
7446         {
7447             sal_Bool eof;
7448             if( file.isEndOfFile( &eof ) != osl::File::E_None )
7449             {
7450                 SAL_WARN( "sw.ww8", "Error reading font file " << fontUrl );
7451                 xOutStream->closeOutput();
7452                 return false;
7453             }
7454             if( eof )
7455                 break;
7456             if( file.read( buffer, 4096, readSize ) != osl::File::E_None )
7457             {
7458                 SAL_WARN( "sw.ww8", "Error reading font file " << fontUrl );
7459                 xOutStream->closeOutput();
7460                 return false;
7461             }
7462             if( readSize == 0 )
7463                 break;
7464             // coverity[overrun-buffer-arg : FALSE] - coverity has difficulty with css::uno::Sequence
7465             xOutStream->writeBytes( uno::Sequence< sal_Int8 >( reinterpret_cast< const sal_Int8* >( buffer ), readSize ));
7466         }
7467         xOutStream->closeOutput();
7468         EmbeddedFontRef ref;
7469         ref.relId = OUStringToOString( GetExport().GetFilter().addRelation( m_pSerializer->getOutputStream(),
7470             oox::getRelationship(Relationship::FONT),
7471             Concat2View("fonts/font" + OUString::number( m_nextFontId ) + ".odttf") ), RTL_TEXTENCODING_UTF8 );
7472         ref.fontKey = fontKeyStr;
7473         m_FontFilesMap[ fontUrl ] = ref;
7474         ++m_nextFontId;
7475     }
7476     m_pSerializer->singleElementNS( XML_w, tag,
7477         FSNS( XML_r, XML_id ), m_FontFilesMap[ fontUrl ].relId,
7478         FSNS( XML_w, XML_fontKey ), m_FontFilesMap[ fontUrl ].fontKey );
7479     return true;
7480 }
7481 
TransHighlightColor(sal_uInt8 nIco)7482 OString DocxAttributeOutput::TransHighlightColor( sal_uInt8 nIco )
7483 {
7484     switch (nIco)
7485     {
7486         case 0: return "none"_ostr; break;
7487         case 1: return "black"_ostr; break;
7488         case 2: return "blue"_ostr; break;
7489         case 3: return "cyan"_ostr; break;
7490         case 4: return "green"_ostr; break;
7491         case 5: return "magenta"_ostr; break;
7492         case 6: return "red"_ostr; break;
7493         case 7: return "yellow"_ostr; break;
7494         case 8: return "white"_ostr; break;
7495         case 9: return "darkBlue"_ostr; break;
7496         case 10: return "darkCyan"_ostr; break;
7497         case 11: return "darkGreen"_ostr; break;
7498         case 12: return "darkMagenta"_ostr; break;
7499         case 13: return "darkRed"_ostr; break;
7500         case 14: return "darkYellow"_ostr; break;
7501         case 15: return "darkGray"_ostr; break;
7502         case 16: return "lightGray"_ostr; break;
7503         default: return OString(); break;
7504     }
7505 }
7506 
NumberingDefinition(sal_uInt16 nId,const SwNumRule & rRule)7507 void DocxAttributeOutput::NumberingDefinition( sal_uInt16 nId, const SwNumRule &rRule )
7508 {
7509     // nId is the same both for abstract numbering definition as well as the
7510     // numbering definition itself
7511     // TODO check that this is actually true & fix if not ;-)
7512     OString aId( OString::number( nId ) );
7513 
7514     m_pSerializer->startElementNS(XML_w, XML_num, FSNS(XML_w, XML_numId), aId);
7515 
7516     m_pSerializer->singleElementNS(XML_w, XML_abstractNumId, FSNS(XML_w, XML_val), aId);
7517 
7518 #if OSL_DEBUG_LEVEL > 1
7519     // TODO ww8 version writes this, anything to do about it here?
7520     if ( rRule.IsContinusNum() )
7521         SAL_INFO("sw", "TODO DocxAttributeOutput::NumberingDefinition()" );
7522 #else
7523     (void) rRule; // to quiet the warning...
7524 #endif
7525 
7526     m_pSerializer->endElementNS( XML_w, XML_num );
7527 }
7528 
7529 // Not all attributes of SwNumFormat are important for export, so can't just use embedded in
7530 // that classes comparison.
lcl_ListLevelsAreDifferentForExport(const SwNumFormat & rFormat1,const SwNumFormat & rFormat2)7531 static bool lcl_ListLevelsAreDifferentForExport(const SwNumFormat & rFormat1, const SwNumFormat & rFormat2)
7532 {
7533     if (rFormat1 == rFormat2)
7534         // They are equal, nothing to do
7535         return false;
7536 
7537     if (!rFormat1.GetCharFormat() != !rFormat2.GetCharFormat())
7538         // One has charformat, other not. they are different
7539         return true;
7540 
7541     if (rFormat1.GetCharFormat() && rFormat2.GetCharFormat())
7542     {
7543         const SwAttrSet & a1 = rFormat1.GetCharFormat()->GetAttrSet();
7544         const SwAttrSet & a2 = rFormat2.GetCharFormat()->GetAttrSet();
7545 
7546         if (!(a1 == a2))
7547             // Difference in charformat: they are different
7548             return true;
7549     }
7550 
7551     // Compare numformats with empty charformats
7552     SwNumFormat modified1 = rFormat1;
7553     SwNumFormat modified2 = rFormat2;
7554     modified1.SetCharFormatName(OUString());
7555     modified2.SetCharFormatName(OUString());
7556     modified1.SetCharFormat(nullptr);
7557     modified2.SetCharFormat(nullptr);
7558     return modified1 != modified2;
7559 }
7560 
OverrideNumberingDefinition(SwNumRule const & rRule,sal_uInt16 const nNum,sal_uInt16 const nAbstractNum,const std::map<size_t,size_t> & rLevelOverrides)7561 void DocxAttributeOutput::OverrideNumberingDefinition(
7562         SwNumRule const& rRule,
7563         sal_uInt16 const nNum, sal_uInt16 const nAbstractNum, const std::map< size_t, size_t > & rLevelOverrides )
7564 {
7565     m_pSerializer->startElementNS(XML_w, XML_num, FSNS(XML_w, XML_numId), OString::number(nNum));
7566 
7567     m_pSerializer->singleElementNS(XML_w, XML_abstractNumId, FSNS(XML_w, XML_val), OString::number(nAbstractNum));
7568 
7569     SwNumRule const& rAbstractRule = *(*m_rExport.m_pUsedNumTable)[nAbstractNum - 1];
7570     sal_uInt8 const nLevels = static_cast<sal_uInt8>(rRule.IsContinusNum()
7571         ? WW8ListManager::nMinLevel : WW8ListManager::nMaxLevel);
7572     sal_uInt8 nPreviousOverrideLevel = 0;
7573     for (sal_uInt8 nLevel = 0; nLevel < nLevels; ++nLevel)
7574     {
7575         const auto levelOverride = rLevelOverrides.find(nLevel);
7576         bool bListsAreDifferent = lcl_ListLevelsAreDifferentForExport(rRule.Get(nLevel), rAbstractRule.Get(nLevel));
7577 
7578         // Export list override only if it is different to abstract one
7579         // or we have a level numbering override
7580         if (bListsAreDifferent || levelOverride != rLevelOverrides.end())
7581         {
7582             // If there are "gaps" in w:lvlOverride numbers, MS Word can have issues with numbering.
7583             // So we need to emit default override tokens up to current one.
7584             while (nPreviousOverrideLevel < nLevel)
7585             {
7586                 const SwNumFormat& rFormat = rRule.Get(nPreviousOverrideLevel);
7587                 m_pSerializer->startElementNS(XML_w, XML_lvlOverride, FSNS(XML_w, XML_ilvl), OString::number(nPreviousOverrideLevel));
7588                 // tdf#153104: absent startOverride is treated by Word as "startOverride value 0".
7589                 m_pSerializer->singleElementNS(XML_w, XML_startOverride, FSNS(XML_w, XML_val), OString::number(rFormat.GetStart()));
7590                 m_pSerializer->endElementNS(XML_w, XML_lvlOverride);
7591                 nPreviousOverrideLevel++;
7592             }
7593 
7594             m_pSerializer->startElementNS(XML_w, XML_lvlOverride, FSNS(XML_w, XML_ilvl), OString::number(nLevel));
7595 
7596             if (bListsAreDifferent)
7597             {
7598                 GetExport().NumberingLevel(rRule, nLevel);
7599             }
7600             if (levelOverride != rLevelOverrides.end())
7601             {
7602                 // list numbering restart override
7603                 m_pSerializer->singleElementNS(XML_w, XML_startOverride,
7604                     FSNS(XML_w, XML_val), OString::number(levelOverride->second));
7605             }
7606 
7607             m_pSerializer->endElementNS(XML_w, XML_lvlOverride);
7608         }
7609     }
7610 
7611     m_pSerializer->endElementNS( XML_w, XML_num );
7612 }
7613 
StartAbstractNumbering(sal_uInt16 nId)7614 void DocxAttributeOutput::StartAbstractNumbering( sal_uInt16 nId )
7615 {
7616     const SwNumRule* pRule = (*m_rExport.m_pUsedNumTable)[nId - 1];
7617     m_bExportingOutline = pRule && pRule->IsOutlineRule();
7618     m_pSerializer->startElementNS( XML_w, XML_abstractNum,
7619             FSNS( XML_w, XML_abstractNumId ), OString::number(nId) );
7620 }
7621 
EndAbstractNumbering()7622 void DocxAttributeOutput::EndAbstractNumbering()
7623 {
7624     m_pSerializer->endElementNS( XML_w, XML_abstractNum );
7625 }
7626 
NumberingLevel(sal_uInt8 nLevel,sal_uInt16 nStart,sal_uInt16 nNumberingType,SvxAdjust eAdjust,const sal_uInt8 *,sal_uInt8 nFollow,const wwFont * pFont,const SfxItemSet * pOutSet,sal_Int16 nIndentAt,sal_Int16 nFirstLineIndex,sal_Int16 nListTabPos,const OUString & rNumberingString,const SvxBrushItem * pBrush,bool isLegal)7627 void DocxAttributeOutput::NumberingLevel( sal_uInt8 nLevel,
7628         sal_uInt16 nStart,
7629         sal_uInt16 nNumberingType,
7630         SvxAdjust eAdjust,
7631         const sal_uInt8 * /*pNumLvlPos*/,
7632         sal_uInt8 nFollow,
7633         const wwFont *pFont,
7634         const SfxItemSet *pOutSet,
7635         sal_Int16 nIndentAt,
7636         sal_Int16 nFirstLineIndex,
7637         sal_Int16 nListTabPos,
7638         const OUString &rNumberingString,
7639         const SvxBrushItem* pBrush,
7640         bool isLegal)
7641 {
7642     m_pSerializer->startElementNS(XML_w, XML_lvl, FSNS(XML_w, XML_ilvl), OString::number(nLevel));
7643 
7644     // start with the nStart value. Do not write w:start if Numbered Lists
7645     // starts from zero.As it's an optional parameter.
7646     // refer ECMA 376 Second edition Part-1
7647     if(0 != nLevel || 0 != nStart)
7648     {
7649         m_pSerializer->singleElementNS( XML_w, XML_start,
7650                 FSNS( XML_w, XML_val ), OString::number(nStart) );
7651     }
7652 
7653     if (m_bExportingOutline)
7654     {
7655         sal_uInt16 nId = m_rExport.m_pStyles->GetHeadingParagraphStyleId( nLevel );
7656         if ( nId != SAL_MAX_UINT16 )
7657             m_pSerializer->singleElementNS( XML_w, XML_pStyle ,
7658                 FSNS( XML_w, XML_val ), m_rExport.m_pStyles->GetStyleId(nId) );
7659     }
7660 
7661     if (isLegal)
7662         m_pSerializer->singleElementNS(XML_w, XML_isLgl);
7663 
7664     // format
7665     OString aCustomFormat;
7666     OString aFormat(lcl_ConvertNumberingType(nNumberingType, pOutSet, aCustomFormat, "decimal"_ostr));
7667 
7668     {
7669         if (aCustomFormat.isEmpty())
7670         {
7671             m_pSerializer->singleElementNS(XML_w, XML_numFmt, FSNS(XML_w, XML_val), aFormat);
7672         }
7673         else
7674         {
7675             m_pSerializer->startElementNS(XML_mc, XML_AlternateContent);
7676             m_pSerializer->startElementNS(XML_mc, XML_Choice, XML_Requires, "w14");
7677 
7678             m_pSerializer->singleElementNS(XML_w, XML_numFmt, FSNS(XML_w, XML_val), aFormat,
7679                                            FSNS(XML_w, XML_format), aCustomFormat);
7680 
7681             m_pSerializer->endElementNS(XML_mc, XML_Choice);
7682             m_pSerializer->startElementNS(XML_mc, XML_Fallback);
7683             m_pSerializer->singleElementNS(XML_w, XML_numFmt, FSNS(XML_w, XML_val), "decimal");
7684             m_pSerializer->endElementNS(XML_mc, XML_Fallback);
7685             m_pSerializer->endElementNS(XML_mc, XML_AlternateContent);
7686         }
7687     }
7688 
7689     // suffix
7690     const char *pSuffix = nullptr;
7691     switch ( nFollow )
7692     {
7693         case 1:  pSuffix = "space";   break;
7694         case 2:  pSuffix = "nothing"; break;
7695         default: /*pSuffix = "tab";*/ break;
7696     }
7697     if ( pSuffix )
7698         m_pSerializer->singleElementNS(XML_w, XML_suff, FSNS(XML_w, XML_val), pSuffix);
7699 
7700     // text
7701     OUStringBuffer aBuffer( rNumberingString.getLength() + WW8ListManager::nMaxLevel );
7702 
7703     const sal_Unicode *pPrev = rNumberingString.getStr();
7704     const sal_Unicode *pIt = rNumberingString.getStr();
7705     while ( pIt < rNumberingString.getStr() + rNumberingString.getLength() )
7706     {
7707         // convert the level values to %NUMBER form
7708         // (we don't use pNumLvlPos at all)
7709         // FIXME so far we support the ww8 limit of levels only
7710         if ( *pIt < sal_Unicode( WW8ListManager::nMaxLevel ) )
7711         {
7712             aBuffer.append( OUString::Concat(std::u16string_view(pPrev, pIt - pPrev))
7713                 + "%"
7714                 + OUString::number(sal_Int32( *pIt ) + 1 ));
7715 
7716             pPrev = pIt + 1;
7717         }
7718         ++pIt;
7719     }
7720     if ( pPrev < pIt )
7721         aBuffer.append( pPrev, pIt - pPrev );
7722 
7723     // If bullet char is empty, set lvlText as empty
7724     if ( rNumberingString == OUStringChar('\0') && nNumberingType == SVX_NUM_CHAR_SPECIAL )
7725     {
7726         m_pSerializer->singleElementNS(XML_w, XML_lvlText, FSNS(XML_w, XML_val), "");
7727     }
7728     else
7729     {
7730         // Writer's "zero width space" suffix is necessary, so that LabelFollowedBy shows up, but Word doesn't require that.
7731         OUString aLevelText = aBuffer.makeStringAndClear();
7732         static OUString aZeroWidthSpace(u'\x200B');
7733         if (aLevelText == aZeroWidthSpace)
7734             aLevelText.clear();
7735         m_pSerializer->singleElementNS(XML_w, XML_lvlText, FSNS(XML_w, XML_val), aLevelText);
7736     }
7737 
7738     // bullet
7739     if (nNumberingType == SVX_NUM_BITMAP && pBrush)
7740     {
7741         int nIndex = m_rExport.GetGrfIndex(*pBrush);
7742         if (nIndex != -1)
7743         {
7744             m_pSerializer->singleElementNS(XML_w, XML_lvlPicBulletId,
7745                     FSNS(XML_w, XML_val), OString::number(nIndex));
7746         }
7747     }
7748 
7749     // justification
7750     const char *pJc;
7751     bool const ecmaDialect = m_rExport.GetFilter().getVersion() == oox::core::ECMA_376_1ST_EDITION;
7752     switch ( eAdjust )
7753     {
7754         case SvxAdjust::Center: pJc = "center"; break;
7755         case SvxAdjust::Right:  pJc = !ecmaDialect ? "end" : "right";  break;
7756         default:                pJc = !ecmaDialect ? "start" : "left";   break;
7757     }
7758     m_pSerializer->singleElementNS(XML_w, XML_lvlJc, FSNS(XML_w, XML_val), pJc);
7759 
7760     // indentation
7761     m_pSerializer->startElementNS(XML_w, XML_pPr);
7762     if( nListTabPos >= 0 )
7763     {
7764         m_pSerializer->startElementNS(XML_w, XML_tabs);
7765         m_pSerializer->singleElementNS( XML_w, XML_tab,
7766                 FSNS( XML_w, XML_val ), "num",
7767                 FSNS( XML_w, XML_pos ), OString::number(nListTabPos) );
7768         m_pSerializer->endElementNS( XML_w, XML_tabs );
7769     }
7770 
7771     sal_Int32 nToken = ecmaDialect ? XML_left : XML_start;
7772     sal_Int32 nIndentToken = nFirstLineIndex > 0 ? XML_firstLine : XML_hanging;
7773     m_pSerializer->singleElementNS( XML_w, XML_ind,
7774             FSNS( XML_w, nToken ), OString::number(nIndentAt),
7775             FSNS( XML_w, nIndentToken ), OString::number(abs(nFirstLineIndex)) );
7776     m_pSerializer->endElementNS( XML_w, XML_pPr );
7777 
7778     // font
7779     if ( pOutSet )
7780     {
7781         m_pSerializer->startElementNS(XML_w, XML_rPr);
7782 
7783         SfxItemSet aTempSet(*pOutSet);
7784         if ( pFont )
7785         {
7786             GetExport().GetId( *pFont ); // ensure font info is written to fontTable.xml
7787             OString aFamilyName( OUStringToOString( pFont->GetFamilyName(), RTL_TEXTENCODING_UTF8 ) );
7788             m_pSerializer->singleElementNS( XML_w, XML_rFonts,
7789                     FSNS( XML_w, XML_ascii ), aFamilyName,
7790                     FSNS( XML_w, XML_hAnsi ), aFamilyName,
7791                     FSNS( XML_w, XML_cs ), aFamilyName,
7792                     FSNS( XML_w, XML_hint ), "default" );
7793             aTempSet.ClearItem(RES_CHRATR_FONT);
7794             aTempSet.ClearItem(RES_CHRATR_CTL_FONT);
7795         }
7796         m_rExport.OutputItemSet(aTempSet, false, true, i18n::ScriptType::LATIN, m_rExport.m_bExportModeRTF);
7797 
7798         WriteCollectedRunProperties();
7799 
7800         m_pSerializer->endElementNS( XML_w, XML_rPr );
7801     }
7802 
7803     // TODO anything to do about nListTabPos?
7804 
7805     m_pSerializer->endElementNS( XML_w, XML_lvl );
7806 }
7807 
CharCaseMap(const SvxCaseMapItem & rCaseMap)7808 void DocxAttributeOutput::CharCaseMap( const SvxCaseMapItem& rCaseMap )
7809 {
7810     switch ( rCaseMap.GetValue() )
7811     {
7812         case SvxCaseMap::SmallCaps:
7813             m_pSerializer->singleElementNS(XML_w, XML_smallCaps);
7814             break;
7815         case SvxCaseMap::Uppercase:
7816             m_pSerializer->singleElementNS(XML_w, XML_caps);
7817             break;
7818         default: // Something that ooxml does not support
7819             m_pSerializer->singleElementNS(XML_w, XML_smallCaps, FSNS(XML_w, XML_val), "false");
7820             m_pSerializer->singleElementNS(XML_w, XML_caps, FSNS(XML_w, XML_val), "false");
7821             break;
7822     }
7823 }
7824 
CharColor(const SvxColorItem & rColorItem)7825 void DocxAttributeOutput::CharColor(const SvxColorItem& rColorItem)
7826 {
7827     const Color aColor = rColorItem.getColor();
7828     const model::ComplexColor aComplexColor = rColorItem.getComplexColor();
7829 
7830     OString aColorString = msfilter::util::ConvertColor(aColor);
7831 
7832     std::string_view pExistingValue;
7833     if (m_pColorAttrList.is() && m_pColorAttrList->getAsView(FSNS(XML_w, XML_val), pExistingValue))
7834     {
7835         assert(aColorString.equalsL(pExistingValue.data(), pExistingValue.size()));
7836         return;
7837     }
7838 
7839     lclAddThemeColorAttributes(m_pColorAttrList, aComplexColor);
7840 
7841     AddToAttrList(m_pColorAttrList, FSNS(XML_w, XML_val), aColorString);
7842     m_nCharTransparence = 255 - aColor.GetAlpha();
7843 }
7844 
CharContour(const SvxContourItem & rContour)7845 void DocxAttributeOutput::CharContour( const SvxContourItem& rContour )
7846 {
7847     if ( rContour.GetValue() )
7848         m_pSerializer->singleElementNS(XML_w, XML_outline);
7849     else
7850         m_pSerializer->singleElementNS(XML_w, XML_outline, FSNS(XML_w, XML_val), "false");
7851 }
7852 
CharCrossedOut(const SvxCrossedOutItem & rCrossedOut)7853 void DocxAttributeOutput::CharCrossedOut( const SvxCrossedOutItem& rCrossedOut )
7854 {
7855     switch ( rCrossedOut.GetStrikeout() )
7856     {
7857         case STRIKEOUT_DOUBLE:
7858             m_pSerializer->singleElementNS(XML_w, XML_dstrike);
7859             break;
7860         case STRIKEOUT_NONE:
7861             m_pSerializer->singleElementNS(XML_w, XML_dstrike, FSNS(XML_w, XML_val), "false");
7862             m_pSerializer->singleElementNS(XML_w, XML_strike, FSNS(XML_w, XML_val), "false");
7863             break;
7864         default:
7865             m_pSerializer->singleElementNS(XML_w, XML_strike);
7866             break;
7867     }
7868 }
7869 
CharEscapement(const SvxEscapementItem & rEscapement)7870 void DocxAttributeOutput::CharEscapement( const SvxEscapementItem& rEscapement )
7871 {
7872     OString sIss;
7873     short nEsc = rEscapement.GetEsc(), nProp = rEscapement.GetProportionalHeight();
7874 
7875     bool bParaStyle = false;
7876     if (m_rExport.m_bStyDef && m_rExport.m_pCurrentStyle)
7877     {
7878         bParaStyle = m_rExport.m_pCurrentStyle->Which() == RES_TXTFMTCOLL;
7879     }
7880 
7881     // Simplify styles to avoid impossible complexity. Import and export as defaults only
7882     if ( m_rExport.m_bStyDef && nEsc && !(bParaStyle && nEsc < 0))
7883     {
7884         nProp = DFLT_ESC_PROP;
7885         nEsc = (nEsc > 0) ? DFLT_ESC_AUTO_SUPER : DFLT_ESC_AUTO_SUB;
7886     }
7887 
7888     if ( !nEsc )
7889     {
7890         sIss = "baseline"_ostr;
7891         nEsc = 0;
7892         nProp = 100;
7893     }
7894     else if ( DFLT_ESC_PROP == nProp || nProp < 1 || nProp > 100 )
7895     {
7896         if ( DFLT_ESC_SUB == nEsc || DFLT_ESC_AUTO_SUB == nEsc )
7897             sIss = "subscript"_ostr;
7898         else if ( DFLT_ESC_SUPER == nEsc || DFLT_ESC_AUTO_SUPER == nEsc )
7899             sIss = "superscript"_ostr;
7900     }
7901     else if ( DFLT_ESC_AUTO_SUPER == nEsc )
7902     {
7903         // Raised by the differences between the ascenders (ascent = baseline to top of highest letter).
7904         // The ascent is generally about 80% of the total font height.
7905         // That is why DFLT_ESC_PROP (58) leads to 33% (DFLT_ESC_SUPER)
7906         nEsc = .8 * (100 - nProp);
7907     }
7908     else if ( DFLT_ESC_AUTO_SUB == nEsc )
7909     {
7910         // Lowered by the differences between the descenders (descent = baseline to bottom of lowest letter).
7911         // The descent is generally about 20% of the total font height.
7912         // That is why DFLT_ESC_PROP (58) leads to 8% (DFLT_ESC_SUB)
7913         nEsc = .2 * -(100 - nProp);
7914     }
7915 
7916     if ( !sIss.isEmpty() )
7917         m_pSerializer->singleElementNS(XML_w, XML_vertAlign, FSNS(XML_w, XML_val), sIss);
7918 
7919     if (!(sIss.isEmpty() || sIss.match("baseline")))
7920         return;
7921 
7922     const SvxFontHeightItem& rItem = m_rExport.GetItem(RES_CHRATR_FONTSIZE);
7923     float fHeight = rItem.GetHeight();
7924     OString sPos = OString::number( round(( fHeight * nEsc ) / 1000) );
7925     m_pSerializer->singleElementNS(XML_w, XML_position, FSNS(XML_w, XML_val), sPos);
7926 
7927     if( ( 100 != nProp || sIss.match( "baseline" ) ) && !m_rExport.m_bFontSizeWritten )
7928     {
7929         OString sSize = OString::number( round(( fHeight * nProp ) / 1000) );
7930         m_pSerializer->singleElementNS(XML_w, XML_sz, FSNS(XML_w, XML_val), sSize);
7931     }
7932 }
7933 
CharFont(const SvxFontItem & rFont)7934 void DocxAttributeOutput::CharFont( const SvxFontItem& rFont)
7935 {
7936     GetExport().GetId( rFont ); // ensure font info is written to fontTable.xml
7937     const OUString& sFontName(rFont.GetFamilyName());
7938     if (sFontName.isEmpty())
7939         return;
7940 
7941     if (m_pFontsAttrList &&
7942         (   m_pFontsAttrList->hasAttribute(FSNS( XML_w, XML_ascii )) ||
7943             m_pFontsAttrList->hasAttribute(FSNS( XML_w, XML_hAnsi ))    )
7944         )
7945     {
7946         // tdf#38778: do to fields output into DOC the font could be added before and after field declaration
7947         // that all sub runs of the field will have correct font inside.
7948         // For DOCX we should do not add the same font information twice in the same node
7949         return;
7950     }
7951 
7952     AddToAttrList( m_pFontsAttrList,
7953         FSNS( XML_w, XML_ascii ), sFontName,
7954         FSNS( XML_w, XML_hAnsi ), sFontName );
7955 }
7956 
CharFontSize(const SvxFontHeightItem & rFontSize)7957 void DocxAttributeOutput::CharFontSize( const SvxFontHeightItem& rFontSize)
7958 {
7959     OString fontSize = OString::number( ( rFontSize.GetHeight() + 5 ) / 10 );
7960 
7961     switch ( rFontSize.Which() )
7962     {
7963         case RES_CHRATR_FONTSIZE:
7964         case RES_CHRATR_CJK_FONTSIZE:
7965             m_pSerializer->singleElementNS(XML_w, XML_sz, FSNS(XML_w, XML_val), fontSize);
7966             break;
7967         case RES_CHRATR_CTL_FONTSIZE:
7968             m_pSerializer->singleElementNS(XML_w, XML_szCs, FSNS(XML_w, XML_val), fontSize);
7969             break;
7970     }
7971 }
7972 
CharKerning(const SvxKerningItem & rKerning)7973 void DocxAttributeOutput::CharKerning( const SvxKerningItem& rKerning )
7974 {
7975     OString aKerning = OString::number(  rKerning.GetValue() );
7976     m_pSerializer->singleElementNS(XML_w, XML_spacing, FSNS(XML_w, XML_val), aKerning);
7977 }
7978 
CharLanguage(const SvxLanguageItem & rLanguage)7979 void DocxAttributeOutput::CharLanguage( const SvxLanguageItem& rLanguage )
7980 {
7981     OUString aLanguageCode(LanguageTag( rLanguage.GetLanguage()).getBcp47MS());
7982 
7983     switch ( rLanguage.Which() )
7984     {
7985         case RES_CHRATR_LANGUAGE:
7986             AddToAttrList( m_pCharLangAttrList, FSNS( XML_w, XML_val ), aLanguageCode );
7987             break;
7988         case RES_CHRATR_CJK_LANGUAGE:
7989             AddToAttrList( m_pCharLangAttrList, FSNS( XML_w, XML_eastAsia ), aLanguageCode );
7990             break;
7991         case RES_CHRATR_CTL_LANGUAGE:
7992             AddToAttrList( m_pCharLangAttrList, FSNS( XML_w, XML_bidi ), aLanguageCode );
7993             break;
7994     }
7995 }
7996 
CharPosture(const SvxPostureItem & rPosture)7997 void DocxAttributeOutput::CharPosture( const SvxPostureItem& rPosture )
7998 {
7999     if ( rPosture.GetPosture() != ITALIC_NONE )
8000         m_pSerializer->singleElementNS(XML_w, XML_i);
8001     else
8002         m_pSerializer->singleElementNS(XML_w, XML_i, FSNS(XML_w, XML_val), "false");
8003 }
8004 
CharShadow(const SvxShadowedItem & rShadow)8005 void DocxAttributeOutput::CharShadow( const SvxShadowedItem& rShadow )
8006 {
8007     if ( rShadow.GetValue() )
8008         m_pSerializer->singleElementNS(XML_w, XML_shadow);
8009     else
8010         m_pSerializer->singleElementNS(XML_w, XML_shadow, FSNS(XML_w, XML_val), "false");
8011 }
8012 
CharUnderline(const SvxUnderlineItem & rUnderline)8013 void DocxAttributeOutput::CharUnderline( const SvxUnderlineItem& rUnderline )
8014 {
8015     const char *pUnderlineValue;
8016 
8017     switch ( rUnderline.GetLineStyle() )
8018     {
8019         case LINESTYLE_SINGLE:         pUnderlineValue = "single";          break;
8020         case LINESTYLE_BOLD:           pUnderlineValue = "thick";           break;
8021         case LINESTYLE_DOUBLE:         pUnderlineValue = "double";          break;
8022         case LINESTYLE_DOTTED:         pUnderlineValue = "dotted";          break;
8023         case LINESTYLE_DASH:           pUnderlineValue = "dash";            break;
8024         case LINESTYLE_DASHDOT:        pUnderlineValue = "dotDash";         break;
8025         case LINESTYLE_DASHDOTDOT:     pUnderlineValue = "dotDotDash";      break;
8026         case LINESTYLE_WAVE:           pUnderlineValue = "wave";            break;
8027         case LINESTYLE_BOLDDOTTED:     pUnderlineValue = "dottedHeavy";     break;
8028         case LINESTYLE_BOLDDASH:       pUnderlineValue = "dashedHeavy";     break;
8029         case LINESTYLE_LONGDASH:       pUnderlineValue = "dashLongHeavy";   break;
8030         case LINESTYLE_BOLDLONGDASH:   pUnderlineValue = "dashLongHeavy";   break;
8031         case LINESTYLE_BOLDDASHDOT:    pUnderlineValue = "dashDotHeavy";    break;
8032         case LINESTYLE_BOLDDASHDOTDOT: pUnderlineValue = "dashDotDotHeavy"; break;
8033         case LINESTYLE_BOLDWAVE:       pUnderlineValue = "wavyHeavy";       break;
8034         case LINESTYLE_DOUBLEWAVE:     pUnderlineValue = "wavyDouble";      break;
8035         case LINESTYLE_NONE:           // fall through
8036         default:                       pUnderlineValue = "none";            break;
8037     }
8038 
8039     Color aUnderlineColor = rUnderline.GetColor();
8040     bool  bUnderlineHasColor = !aUnderlineColor.IsTransparent();
8041     if (bUnderlineHasColor)
8042     {
8043         model::ComplexColor const& rComplexColor = rUnderline.getComplexColor();
8044         // Underline has a color
8045         rtl::Reference<FastAttributeList> pAttrList = FastSerializerHelper::createAttrList();
8046         pAttrList->add(FSNS(XML_w, XML_val), pUnderlineValue);
8047         pAttrList->add(FSNS(XML_w, XML_color), msfilter::util::ConvertColor(aUnderlineColor));
8048         lclAddThemeColorAttributes(pAttrList, rComplexColor);
8049         m_pSerializer->singleElementNS(XML_w, XML_u, pAttrList);
8050 
8051     }
8052     else
8053     {
8054         // Underline has no color
8055         m_pSerializer->singleElementNS(XML_w, XML_u, FSNS(XML_w, XML_val), pUnderlineValue);
8056     }
8057 }
8058 
CharWeight(const SvxWeightItem & rWeight)8059 void DocxAttributeOutput::CharWeight( const SvxWeightItem& rWeight )
8060 {
8061     if ( rWeight.GetWeight() == WEIGHT_BOLD )
8062         m_pSerializer->singleElementNS(XML_w, XML_b);
8063     else
8064         m_pSerializer->singleElementNS(XML_w, XML_b, FSNS(XML_w, XML_val), "false");
8065 }
8066 
CharAutoKern(const SvxAutoKernItem & rAutoKern)8067 void DocxAttributeOutput::CharAutoKern( const SvxAutoKernItem& rAutoKern )
8068 {
8069     // auto kerning is bound to a minimum font size in Word - but is just a boolean in Writer :-(
8070     // kerning is based on half-point sizes, so 2 enables kerning for fontsize 1pt or higher. (1 is treated as size 12, and 0 is treated as disabled.)
8071     const OString sFontSize = OString::number( static_cast<sal_uInt32>(rAutoKern.GetValue()) * 2 );
8072     m_pSerializer->singleElementNS(XML_w, XML_kern, FSNS(XML_w, XML_val), sFontSize);
8073 }
8074 
CharAnimatedText(const SvxBlinkItem & rBlink)8075 void DocxAttributeOutput::CharAnimatedText( const SvxBlinkItem& rBlink )
8076 {
8077     if ( rBlink.GetValue() )
8078         m_pSerializer->singleElementNS(XML_w, XML_effect, FSNS(XML_w, XML_val), "blinkBackground");
8079     else
8080         m_pSerializer->singleElementNS(XML_w, XML_effect, FSNS(XML_w, XML_val), "none");
8081 }
8082 
8083 constexpr OUStringLiteral MSWORD_CH_SHADING_FILL = u"FFFFFF"; // The attribute w:fill of w:shd, for MS-Word's character shading,
8084 constexpr OUStringLiteral MSWORD_CH_SHADING_COLOR = u"auto"; // The attribute w:color of w:shd, for MS-Word's character shading,
8085 constexpr OUStringLiteral MSWORD_CH_SHADING_VAL = u"pct15"; // The attribute w:value of w:shd, for MS-Word's character shading,
8086 
CharBackground(const SvxBrushItem & rBrush)8087 void DocxAttributeOutput::CharBackground( const SvxBrushItem& rBrush )
8088 {
8089     // Check if the brush shading pattern is 'PCT15'. If so - write it back to the DOCX
8090     if (rBrush.GetShadingValue() == ShadingPattern::PCT15)
8091     {
8092         m_pSerializer->singleElementNS( XML_w, XML_shd,
8093             FSNS( XML_w, XML_val ), MSWORD_CH_SHADING_VAL,
8094             FSNS( XML_w, XML_color ), MSWORD_CH_SHADING_COLOR,
8095             FSNS( XML_w, XML_fill ), MSWORD_CH_SHADING_FILL );
8096     }
8097     else
8098     {
8099         m_pSerializer->singleElementNS( XML_w, XML_shd,
8100             FSNS( XML_w, XML_fill ), msfilter::util::ConvertColor(rBrush.GetColor()),
8101             FSNS( XML_w, XML_val ), "clear" );
8102     }
8103 }
8104 
CharFontCJK(const SvxFontItem & rFont)8105 void DocxAttributeOutput::CharFontCJK( const SvxFontItem& rFont )
8106 {
8107     if (m_pFontsAttrList && m_pFontsAttrList->hasAttribute(FSNS(XML_w, XML_eastAsia)))
8108     {
8109         // tdf#38778: do to fields output into DOC the font could be added before and after field declaration
8110         // that all sub runs of the field will have correct font inside.
8111         // For DOCX we should do not add the same font information twice in the same node
8112         return;
8113     }
8114 
8115     AddToAttrList( m_pFontsAttrList, FSNS( XML_w, XML_eastAsia ), rFont.GetFamilyName() );
8116 }
8117 
CharPostureCJK(const SvxPostureItem & rPosture)8118 void DocxAttributeOutput::CharPostureCJK( const SvxPostureItem& rPosture )
8119 {
8120     if ( rPosture.GetPosture() != ITALIC_NONE )
8121         m_pSerializer->singleElementNS(XML_w, XML_i);
8122     else
8123         m_pSerializer->singleElementNS(XML_w, XML_i, FSNS(XML_w, XML_val), "false");
8124 }
8125 
CharWeightCJK(const SvxWeightItem & rWeight)8126 void DocxAttributeOutput::CharWeightCJK( const SvxWeightItem& rWeight )
8127 {
8128     if ( rWeight.GetWeight() == WEIGHT_BOLD )
8129         m_pSerializer->singleElementNS(XML_w, XML_b);
8130     else
8131         m_pSerializer->singleElementNS(XML_w, XML_b, FSNS(XML_w, XML_val), "false");
8132 }
8133 
CharFontCTL(const SvxFontItem & rFont)8134 void DocxAttributeOutput::CharFontCTL( const SvxFontItem& rFont )
8135 {
8136     if (m_pFontsAttrList && m_pFontsAttrList->hasAttribute(FSNS(XML_w, XML_cs)))
8137     {
8138         // tdf#38778: do to fields output into DOC the font could be added before and after field declaration
8139         // that all sub runs of the field will have correct font inside.
8140         // For DOCX we should do not add the same font information twice in the same node
8141         return;
8142     }
8143 
8144     AddToAttrList( m_pFontsAttrList, FSNS( XML_w, XML_cs ), rFont.GetFamilyName() );
8145 }
8146 
CharPostureCTL(const SvxPostureItem & rPosture)8147 void DocxAttributeOutput::CharPostureCTL( const SvxPostureItem& rPosture)
8148 {
8149     if ( rPosture.GetPosture() != ITALIC_NONE )
8150         m_pSerializer->singleElementNS(XML_w, XML_iCs);
8151     else
8152         m_pSerializer->singleElementNS(XML_w, XML_iCs, FSNS(XML_w, XML_val), "false");
8153 }
8154 
CharWeightCTL(const SvxWeightItem & rWeight)8155 void DocxAttributeOutput::CharWeightCTL( const SvxWeightItem& rWeight )
8156 {
8157     if ( rWeight.GetWeight() == WEIGHT_BOLD )
8158         m_pSerializer->singleElementNS(XML_w, XML_bCs);
8159     else
8160         m_pSerializer->singleElementNS(XML_w, XML_bCs, FSNS(XML_w, XML_val), "false");
8161 }
8162 
CharBidiRTL(const SfxPoolItem &)8163 void DocxAttributeOutput::CharBidiRTL( const SfxPoolItem& )
8164 {
8165 }
8166 
CharIdctHint(const SfxPoolItem &)8167 void DocxAttributeOutput::CharIdctHint( const SfxPoolItem& )
8168 {
8169 }
8170 
CharRotate(const SvxCharRotateItem & rRotate)8171 void DocxAttributeOutput::CharRotate( const SvxCharRotateItem& rRotate)
8172 {
8173     // Not rotated?
8174     if ( !rRotate.GetValue())
8175         return;
8176 
8177     AddToAttrList( m_pEastAsianLayoutAttrList, FSNS( XML_w, XML_vert ), "true" );
8178 
8179     if (rRotate.IsFitToLine())
8180         AddToAttrList( m_pEastAsianLayoutAttrList, FSNS( XML_w, XML_vertCompress ), "true" );
8181 }
8182 
CharEmphasisMark(const SvxEmphasisMarkItem & rEmphasisMark)8183 void DocxAttributeOutput::CharEmphasisMark( const SvxEmphasisMarkItem& rEmphasisMark )
8184 {
8185     const char *pEmphasis;
8186     const FontEmphasisMark v = rEmphasisMark.GetEmphasisMark();
8187 
8188     if (v == (FontEmphasisMark::Dot | FontEmphasisMark::PosAbove))
8189         pEmphasis = "dot";
8190     else if (v == (FontEmphasisMark::Accent | FontEmphasisMark::PosAbove))
8191         pEmphasis = "comma";
8192     else if (v == (FontEmphasisMark::Circle | FontEmphasisMark::PosAbove))
8193         pEmphasis = "circle";
8194     else if (v == (FontEmphasisMark::Dot|FontEmphasisMark::PosBelow))
8195         pEmphasis = "underDot";
8196     else
8197         pEmphasis = "none";
8198 
8199     m_pSerializer->singleElementNS(XML_w, XML_em, FSNS(XML_w, XML_val), pEmphasis);
8200 }
8201 
CharTwoLines(const SvxTwoLinesItem & rTwoLines)8202 void DocxAttributeOutput::CharTwoLines( const SvxTwoLinesItem& rTwoLines )
8203 {
8204     if ( !rTwoLines.GetValue() )
8205         return;
8206 
8207     AddToAttrList( m_pEastAsianLayoutAttrList, FSNS( XML_w, XML_combine ), "true" );
8208 
8209     sal_Unicode cStart = rTwoLines.GetStartBracket();
8210     sal_Unicode cEnd = rTwoLines.GetEndBracket();
8211 
8212     if (!cStart && !cEnd)
8213         return;
8214 
8215     std::string_view sBracket;
8216     if ((cStart == '{') || (cEnd == '}'))
8217         sBracket = "curly";
8218     else if ((cStart == '<') || (cEnd == '>'))
8219         sBracket = "angle";
8220     else if ((cStart == '[') || (cEnd == ']'))
8221         sBracket = "square";
8222     else
8223         sBracket = "round";
8224     AddToAttrList( m_pEastAsianLayoutAttrList, FSNS( XML_w, XML_combineBrackets ), sBracket );
8225 }
8226 
CharScaleWidth(const SvxCharScaleWidthItem & rScaleWidth)8227 void DocxAttributeOutput::CharScaleWidth( const SvxCharScaleWidthItem& rScaleWidth )
8228 {
8229     // Clamp CharScaleWidth to OOXML limits ([1..600])
8230     const sal_Int16 nScaleWidth( std::max<sal_Int16>( 1,
8231         std::min<sal_Int16>( rScaleWidth.GetValue(), 600 ) ) );
8232     m_pSerializer->singleElementNS( XML_w, XML_w,
8233         FSNS( XML_w, XML_val ), OString::number(nScaleWidth) );
8234 }
8235 
CharRelief(const SvxCharReliefItem & rRelief)8236 void DocxAttributeOutput::CharRelief( const SvxCharReliefItem& rRelief )
8237 {
8238     switch ( rRelief.GetValue() )
8239     {
8240         case FontRelief::Embossed:
8241             m_pSerializer->singleElementNS(XML_w, XML_emboss);
8242             break;
8243         case FontRelief::Engraved:
8244             m_pSerializer->singleElementNS(XML_w, XML_imprint);
8245             break;
8246         default:
8247             m_pSerializer->singleElementNS(XML_w, XML_emboss, FSNS(XML_w, XML_val), "false");
8248             m_pSerializer->singleElementNS(XML_w, XML_imprint, FSNS(XML_w, XML_val), "false");
8249             break;
8250     }
8251 }
8252 
CharHidden(const SvxCharHiddenItem & rHidden)8253 void DocxAttributeOutput::CharHidden( const SvxCharHiddenItem& rHidden )
8254 {
8255     if ( rHidden.GetValue() )
8256         m_pSerializer->singleElementNS(XML_w, XML_vanish);
8257     else
8258         m_pSerializer->singleElementNS(XML_w, XML_vanish, FSNS(XML_w, XML_val), "false");
8259 }
8260 
CharBorder(const SvxBorderLine * pAllBorder,const sal_uInt16 nDist,const bool bShadow)8261 void DocxAttributeOutput::CharBorder(
8262     const SvxBorderLine* pAllBorder, const sal_uInt16 nDist, const bool bShadow )
8263 {
8264     css::table::BorderLine2 rStyleBorder;
8265     const SvxBoxItem* pInherited = nullptr;
8266     if ( GetExport().m_bStyDef && GetExport().m_pCurrentStyle && GetExport().m_pCurrentStyle->DerivedFrom() )
8267         pInherited = GetExport().m_pCurrentStyle->DerivedFrom()->GetAttrSet().GetItem<SvxBoxItem>(RES_CHRATR_BOX);
8268     else if ( m_rExport.m_pChpIter ) // incredibly undocumented, but this is the character-style info, right?
8269     {
8270         if (const SvxBoxItem* pPoolItem = GetExport().m_pChpIter->HasTextItem(RES_CHRATR_BOX))
8271         {
8272             pInherited = pPoolItem;
8273         }
8274     }
8275 
8276     if ( pInherited )
8277         rStyleBorder = SvxBoxItem::SvxLineToLine(pInherited->GetRight(), false);
8278 
8279     impl_borderLine( m_pSerializer, XML_bdr, pAllBorder, nDist, bShadow, &rStyleBorder );
8280 }
8281 
CharHighlight(const SvxBrushItem & rHighlight)8282 void DocxAttributeOutput::CharHighlight( const SvxBrushItem& rHighlight )
8283 {
8284     const OString sColor = TransHighlightColor( msfilter::util::TransColToIco(rHighlight.GetColor()) );
8285     if ( !sColor.isEmpty() )
8286     {
8287         m_pSerializer->singleElementNS(XML_w, XML_highlight, FSNS(XML_w, XML_val), sColor);
8288     }
8289 }
8290 
TextINetFormat(const SwFormatINetFormat & rLink)8291 void DocxAttributeOutput::TextINetFormat( const SwFormatINetFormat& rLink )
8292 {
8293     const SwCharFormat* pFormat = m_rExport.m_rDoc.FindCharFormatByName(rLink.GetINetFormat());
8294     if (pFormat)
8295     {
8296         OString aStyleId(m_rExport.m_pStyles->GetStyleId(m_rExport.GetId(pFormat)));
8297         if (!aStyleId.equalsIgnoreAsciiCase("DefaultStyle"))
8298             m_pSerializer->singleElementNS(XML_w, XML_rStyle, FSNS(XML_w, XML_val), aStyleId);
8299     }
8300 }
8301 
TextCharFormat(const SwFormatCharFormat & rCharFormat)8302 void DocxAttributeOutput::TextCharFormat( const SwFormatCharFormat& rCharFormat )
8303 {
8304     OString aStyleId(m_rExport.m_pStyles->GetStyleId(m_rExport.GetId(rCharFormat.GetCharFormat())));
8305 
8306     m_pSerializer->singleElementNS(XML_w, XML_rStyle, FSNS(XML_w, XML_val), aStyleId);
8307 }
8308 
RefField(const SwField & rField,const OUString & rRef)8309 void DocxAttributeOutput::RefField( const SwField&  rField, const OUString& rRef )
8310 {
8311     SwFieldIds nType = rField.GetTyp( )->Which( );
8312     if ( nType == SwFieldIds::GetExp )
8313     {
8314         OUString sCmd = FieldString( ww::eREF ) +
8315             "\"" + rRef + "\" ";
8316 
8317         m_rExport.OutputField( &rField, ww::eREF, sCmd );
8318     }
8319 
8320     // There is nothing to do here for the set fields
8321 }
8322 
HiddenField(const SwField &)8323 void DocxAttributeOutput::HiddenField(const SwField& /*rField*/)
8324 {
8325     SAL_INFO("sw.ww8", "TODO DocxAttributeOutput::HiddenField()" );
8326 }
8327 
PostitField(const SwField * pField)8328 void DocxAttributeOutput::PostitField( const SwField* pField )
8329 {
8330     assert( dynamic_cast< const SwPostItField* >( pField ));
8331     const SwPostItField* pPostItField = static_cast<const SwPostItField*>(pField);
8332     sal_Int32 nId = 0;
8333     auto it = m_rOpenedAnnotationMarksIds.find(pPostItField->GetName());
8334     if (it != m_rOpenedAnnotationMarksIds.end())
8335         // If the postit field has an annotation mark associated, we already have an id.
8336         nId = it->second;
8337     else
8338         // Otherwise get a new one.
8339         nId = m_nNextAnnotationMarkId++;
8340     m_postitFields.emplace_back(pPostItField, PostItDOCXData{ nId });
8341 }
8342 
WritePostitFieldReference()8343 void DocxAttributeOutput::WritePostitFieldReference()
8344 {
8345     while( m_postitFieldsMaxId < m_postitFields.size())
8346     {
8347         OString idstr = OString::number(m_postitFields[m_postitFieldsMaxId].second.id);
8348 
8349         // In case this file is inside annotation marks, we want to write the
8350         // comment reference after the annotation mark is closed, not here.
8351         const OUString& idname = m_postitFields[m_postitFieldsMaxId].first->GetName();
8352         auto it = m_rOpenedAnnotationMarksIds.find( idname );
8353         if ( it == m_rOpenedAnnotationMarksIds.end(  ) )
8354             m_pSerializer->singleElementNS(XML_w, XML_commentReference, FSNS(XML_w, XML_id), idstr);
8355         ++m_postitFieldsMaxId;
8356     }
8357 }
8358 
WritePostitFields()8359 DocxAttributeOutput::hasProperties DocxAttributeOutput::WritePostitFields()
8360 {
8361     bool bRemovePersonalInfo = SvtSecurityOptions::IsOptionSet(
8362         SvtSecurityOptions::EOption::DocWarnRemovePersonalInfo ) && !SvtSecurityOptions::IsOptionSet(
8363             SvtSecurityOptions::EOption::DocWarnKeepRedlineInfo);
8364 
8365     hasProperties eResult = hasProperties::no;
8366     for (auto& [f1, data1] : m_postitFields)
8367     {
8368         if (f1->GetParentId() != 0)
8369         {
8370             for (size_t i = 0; i < m_postitFields.size(); i++)
8371             {
8372                 auto& [f2, data2] = m_postitFields[i];
8373                 if (f2->GetParaId() == f1->GetParentId())
8374                 {
8375                     data2.parentStatus = ParentStatus::IsParent;
8376                     data1.parentStatus = ParentStatus::HasParent;
8377                     data1.parentIndex = i;
8378                     break;
8379                 }
8380             }
8381         }
8382     }
8383     for (auto& [f, data] : m_postitFields)
8384     {
8385         const DateTime aDateTime = f->GetDateTime();
8386         bool bNoDate = bRemovePersonalInfo ||
8387             ( aDateTime.GetYear() == 1970 && aDateTime.GetMonth() == 1 && aDateTime.GetDay() == 1 );
8388 
8389         rtl::Reference<sax_fastparser::FastAttributeList> pAttributeList
8390             = sax_fastparser::FastSerializerHelper::createAttrList();
8391 
8392         pAttributeList->add(FSNS( XML_w, XML_id ), OString::number(data.id));
8393         pAttributeList->add(FSNS( XML_w, XML_author ), bRemovePersonalInfo
8394                  ? "Author" + OString::number( GetExport().GetInfoID(f->GetPar1()) )
8395                  : f->GetPar1().toUtf8());
8396         if (!bNoDate)
8397             pAttributeList->add(FSNS( XML_w, XML_date ), DateTimeToOString( aDateTime ));
8398         pAttributeList->add(FSNS( XML_w, XML_initials ), bRemovePersonalInfo
8399                  ? OString::number( GetExport().GetInfoID(f->GetInitials()) )
8400                  : f->GetInitials().toUtf8());
8401         m_pSerializer->startElementNS( XML_w, XML_comment, pAttributeList );
8402 
8403         // Make sure to give parent/child fields a paraId
8404         const bool bNeedParaId = f->GetResolved() || data.parentStatus != ParentStatus::None;
8405         if (bNeedParaId)
8406             eResult = hasProperties::yes;
8407 
8408         if (f->GetTextObject() != nullptr)
8409         {
8410             // richtext
8411             data.lastParaId = GetExport().WriteOutliner(*f->GetTextObject(), TXT_ATN, bNeedParaId);
8412         }
8413         else
8414         {
8415             // just plain text - eg. when the field was created via the
8416             // .uno:InsertAnnotation API
8417             std::optional<OUString> aParaId;
8418             if (bNeedParaId)
8419             {
8420                 data.lastParaId = m_nNextParaId++;
8421                 aParaId = NumberToHexBinary(data.lastParaId);
8422             }
8423             m_pSerializer->startElementNS(XML_w, XML_p, FSNS(XML_w14, XML_paraId), aParaId);
8424             m_pSerializer->startElementNS(XML_w, XML_r);
8425             RunText(f->GetText());
8426             m_pSerializer->endElementNS(XML_w, XML_r);
8427             m_pSerializer->endElementNS(XML_w, XML_p);
8428         }
8429 
8430         m_pSerializer->endElementNS( XML_w, XML_comment );
8431     }
8432     return eResult;
8433 }
8434 
WritePostItFieldsResolved()8435 void DocxAttributeOutput::WritePostItFieldsResolved()
8436 {
8437     for (auto& [f, data] : m_postitFields)
8438     {
8439         // Parent fields don't need to be exported here if they don't have a resolved attribute
8440         if (!f->GetResolved() && data.parentStatus != ParentStatus::HasParent)
8441             continue;
8442         OUString idstr = NumberToHexBinary(data.lastParaId);
8443         std::optional<OUString> sDone, sParentId;
8444         if (f->GetParentId() != 0)
8445         {
8446             if (data.parentStatus == ParentStatus::HasParent)
8447             {
8448                 // Since parent fields have been resolved first, they should already have an id
8449                 const PostItDOCXData& aParentFieldData = m_postitFields[data.parentIndex].second;
8450                 sParentId = NumberToHexBinary(aParentFieldData.lastParaId);
8451             }
8452             else
8453             {
8454                 SAL_WARN("sw.ww8", "SwPostItField has a parent id, but a matching parent was not found");
8455             }
8456         }
8457         if (f->GetResolved())
8458             sDone = "1";
8459         m_pSerializer->singleElementNS(XML_w15, XML_commentEx,
8460             FSNS(XML_w15, XML_paraId), idstr,
8461             FSNS(XML_w15, XML_done), sDone,
8462             FSNS(XML_w15, XML_paraIdParent), sParentId);
8463     }
8464 }
8465 
DropdownField(const SwField * pField)8466 bool DocxAttributeOutput::DropdownField( const SwField* pField )
8467 {
8468     ww::eField eType = ww::eFORMDROPDOWN;
8469     OUString sCmd = FieldString( eType  );
8470     GetExport( ).OutputField( pField, eType, sCmd );
8471 
8472     return false;
8473 }
8474 
PlaceholderField(const SwField * pField)8475 bool DocxAttributeOutput::PlaceholderField( const SwField* pField )
8476 {
8477     assert( m_PendingPlaceholder == nullptr );
8478     m_PendingPlaceholder = pField;
8479     return false; // do not expand
8480 }
8481 
WritePendingPlaceholder()8482 void DocxAttributeOutput::WritePendingPlaceholder()
8483 {
8484     if( m_PendingPlaceholder == nullptr )
8485         return;
8486     const SwField* pField = m_PendingPlaceholder;
8487     m_PendingPlaceholder = nullptr;
8488     m_pSerializer->startElementNS(XML_w, XML_sdt);
8489     m_pSerializer->startElementNS(XML_w, XML_sdtPr);
8490     if( !pField->GetPar2().isEmpty())
8491         m_pSerializer->singleElementNS(XML_w, XML_alias, FSNS(XML_w, XML_val), pField->GetPar2());
8492     m_pSerializer->singleElementNS(XML_w, XML_temporary);
8493     m_pSerializer->singleElementNS(XML_w, XML_showingPlcHdr);
8494     m_pSerializer->singleElementNS(XML_w, XML_text);
8495     m_pSerializer->endElementNS( XML_w, XML_sdtPr );
8496     m_pSerializer->startElementNS(XML_w, XML_sdtContent);
8497     m_pSerializer->startElementNS(XML_w, XML_r);
8498     RunText( pField->GetPar1());
8499     m_pSerializer->endElementNS( XML_w, XML_r );
8500     m_pSerializer->endElementNS( XML_w, XML_sdtContent );
8501     m_pSerializer->endElementNS( XML_w, XML_sdt );
8502 }
8503 
SetField(const SwField & rField,ww::eField eType,const OUString & rCmd)8504 void DocxAttributeOutput::SetField( const SwField& rField, ww::eField eType, const OUString& rCmd )
8505 {
8506     // field bookmarks are handled in the EndRun method
8507     GetExport().OutputField(&rField, eType, rCmd );
8508 }
8509 
WriteExpand(const SwField * pField)8510 void DocxAttributeOutput::WriteExpand( const SwField* pField )
8511 {
8512     // Will be written in the next End Run
8513     m_rExport.OutputField( pField, ww::eUNKNOWN, OUString() );
8514 }
8515 
WriteField_Impl(const SwField * const pField,ww::eField const eType,const OUString & rFieldCmd,FieldFlags const nMode,OUString const * const pBookmarkName)8516 void DocxAttributeOutput::WriteField_Impl(const SwField *const pField,
8517     ww::eField const eType, const OUString& rFieldCmd, FieldFlags const nMode,
8518     OUString const*const pBookmarkName)
8519 {
8520     if (m_bPreventDoubleFieldsHandling)
8521         return;
8522 
8523     struct FieldInfos infos;
8524     if (pField)
8525         infos.pField = pField->CopyField();
8526     infos.sCmd = rFieldCmd;
8527     infos.eType = eType;
8528     infos.bClose = bool(FieldFlags::Close & nMode);
8529     infos.bSep = bool(FieldFlags::CmdEnd & nMode);
8530     infos.bOpen = bool(FieldFlags::Start & nMode);
8531     m_Fields.push_back( infos );
8532 
8533     if (pBookmarkName)
8534     {
8535         m_sFieldBkm = *pBookmarkName;
8536     }
8537 
8538     if ( !pField )
8539         return;
8540 
8541     SwFieldIds nType = pField->GetTyp( )->Which( );
8542     sal_uInt16 nSubType = pField->GetSubType();
8543 
8544     // TODO Any other field types here ?
8545     if ( ( nType == SwFieldIds::SetExp ) && ( nSubType & nsSwGetSetExpType::GSE_STRING ) )
8546     {
8547         const SwSetExpField *pSet = static_cast<const SwSetExpField*>( pField );
8548         m_sFieldBkm = pSet->GetPar1( );
8549     }
8550     else if ( nType == SwFieldIds::Dropdown )
8551     {
8552         const SwDropDownField* pDropDown = static_cast<const SwDropDownField*>( pField );
8553         m_sFieldBkm = pDropDown->GetName( );
8554     }
8555 }
8556 
WriteFormData_Impl(const::sw::mark::IFieldmark & rFieldmark)8557 void DocxAttributeOutput::WriteFormData_Impl( const ::sw::mark::IFieldmark& rFieldmark )
8558 {
8559     if ( !m_Fields.empty() )
8560         m_Fields.begin()->pFieldmark = &rFieldmark;
8561 }
8562 
WriteBookmarks_Impl(std::vector<OUString> & rStarts,std::vector<OUString> & rEnds,const SwRedlineData * pRedlineData)8563 void DocxAttributeOutput::WriteBookmarks_Impl( std::vector< OUString >& rStarts, std::vector< OUString >& rEnds, const SwRedlineData* pRedlineData )
8564 {
8565     for ( const OUString & name : rStarts )
8566     {
8567         if (name.startsWith("permission-for-group:") ||
8568             name.startsWith("permission-for-user:"))
8569         {
8570             m_rPermissionsStart.push_back(name);
8571         }
8572         else
8573         {
8574             m_rBookmarksStart.push_back(name);
8575             m_pMoveRedlineData = const_cast<SwRedlineData*>(pRedlineData);
8576         }
8577     }
8578     rStarts.clear();
8579 
8580     for ( const OUString & name : rEnds )
8581     {
8582         if (name.startsWith("permission-for-group:") ||
8583             name.startsWith("permission-for-user:"))
8584         {
8585             m_rPermissionsEnd.push_back(name);
8586         }
8587         else
8588         {
8589             m_rBookmarksEnd.push_back(name);
8590         }
8591     }
8592     rEnds.clear();
8593 }
8594 
WriteFinalBookmarks_Impl(std::vector<OUString> & rStarts,std::vector<OUString> & rEnds)8595 void DocxAttributeOutput::WriteFinalBookmarks_Impl( std::vector< OUString >& rStarts, std::vector< OUString >& rEnds )
8596 {
8597     for ( const OUString & name : rStarts )
8598     {
8599         if (name.startsWith("permission-for-group:") ||
8600             name.startsWith("permission-for-user:"))
8601         {
8602             m_rPermissionsStart.push_back(name);
8603         }
8604         else
8605         {
8606             m_rFinalBookmarksStart.push_back(name);
8607         }
8608     }
8609     rStarts.clear();
8610 
8611     for ( const OUString & name : rEnds )
8612     {
8613         if (name.startsWith("permission-for-group:") ||
8614             name.startsWith("permission-for-user:"))
8615         {
8616             m_rPermissionsEnd.push_back(name);
8617         }
8618         else
8619         {
8620             m_rFinalBookmarksEnd.push_back(name);
8621         }
8622     }
8623     rEnds.clear();
8624 }
8625 
WriteAnnotationMarks_Impl(std::vector<OUString> & rStarts,std::vector<OUString> & rEnds)8626 void DocxAttributeOutput::WriteAnnotationMarks_Impl( std::vector< OUString >& rStarts,
8627         std::vector< OUString >& rEnds )
8628 {
8629     m_rAnnotationMarksStart.insert(m_rAnnotationMarksStart.end(), rStarts.begin(), rStarts.end());
8630     rStarts.clear();
8631 
8632     m_rAnnotationMarksEnd.insert(m_rAnnotationMarksEnd.end(), rEnds.begin(), rEnds.end());
8633     rEnds.clear();
8634 }
8635 
TextFootnote_Impl(const SwFormatFootnote & rFootnote)8636 void DocxAttributeOutput::TextFootnote_Impl( const SwFormatFootnote& rFootnote )
8637 {
8638     const SwEndNoteInfo& rInfo = rFootnote.IsEndNote()?
8639         m_rExport.m_rDoc.GetEndNoteInfo(): m_rExport.m_rDoc.GetFootnoteInfo();
8640 
8641     // footnote/endnote run properties
8642     const SwCharFormat* pCharFormat = rInfo.GetAnchorCharFormat( m_rExport.m_rDoc );
8643 
8644     OString aStyleId(m_rExport.m_pStyles->GetStyleId(m_rExport.GetId(pCharFormat)));
8645 
8646     m_pSerializer->singleElementNS(XML_w, XML_rStyle, FSNS(XML_w, XML_val), aStyleId);
8647 
8648     // remember the footnote/endnote to
8649     // 1) write the footnoteReference/endnoteReference in EndRunProperties()
8650     // 2) be able to dump them all to footnotes.xml/endnotes.xml
8651     if ( !rFootnote.IsEndNote() && m_rExport.m_rDoc.GetFootnoteInfo().m_ePos != FTNPOS_CHAPTER )
8652         m_pFootnotesList->add( rFootnote );
8653     else
8654         m_pEndnotesList->add( rFootnote );
8655 }
8656 
FootnoteEndnoteReference()8657 void DocxAttributeOutput::FootnoteEndnoteReference()
8658 {
8659     sal_Int32 nId;
8660     const SwFormatFootnote *pFootnote = m_pFootnotesList->getCurrent( nId );
8661     sal_Int32 nToken = XML_footnoteReference;
8662 
8663     // both cannot be set at the same time - if they are, it's a bug
8664     if ( !pFootnote )
8665     {
8666         pFootnote = m_pEndnotesList->getCurrent( nId );
8667         nToken = XML_endnoteReference;
8668     }
8669 
8670     if ( !pFootnote )
8671         return;
8672 
8673     // write it
8674     if ( pFootnote->GetNumStr().isEmpty() )
8675     {
8676         // autonumbered
8677         m_pSerializer->singleElementNS(XML_w, nToken, FSNS(XML_w, XML_id), OString::number(nId));
8678     }
8679     else
8680     {
8681         // not autonumbered
8682         m_pSerializer->singleElementNS( XML_w, nToken,
8683                 FSNS( XML_w, XML_customMarkFollows ), "1",
8684                 FSNS( XML_w, XML_id ), OString::number(nId) );
8685 
8686         RunText( pFootnote->GetNumStr() );
8687     }
8688 }
8689 
WriteFootnoteSeparatorHeight(::sax_fastparser::FSHelperPtr const & pSerializer,SwTwips const nHeight)8690 static void WriteFootnoteSeparatorHeight(
8691     ::sax_fastparser::FSHelperPtr const& pSerializer, SwTwips const nHeight)
8692 {
8693     // try to get the height by setting font size of the paragraph
8694     if (nHeight != 0)
8695     {
8696         pSerializer->startElementNS(XML_w, XML_pPr);
8697         pSerializer->startElementNS(XML_w, XML_rPr);
8698         pSerializer->singleElementNS(XML_w, XML_sz, FSNS(XML_w, XML_val),
8699             OString::number((nHeight + 5) / 10));
8700         pSerializer->endElementNS(XML_w, XML_rPr);
8701         pSerializer->endElementNS(XML_w, XML_pPr);
8702     }
8703 }
8704 
FootnotesEndnotes(bool bFootnotes)8705 void DocxAttributeOutput::FootnotesEndnotes( bool bFootnotes )
8706 {
8707     const FootnotesVector& rVector = bFootnotes? m_pFootnotesList->getVector(): m_pEndnotesList->getVector();
8708 
8709     sal_Int32 nBody = bFootnotes? XML_footnotes: XML_endnotes;
8710     sal_Int32 nItem = bFootnotes? XML_footnote:  XML_endnote;
8711 
8712     m_pSerializer->startElementNS( XML_w, nBody, m_rExport.MainXmlNamespaces() );
8713 
8714     sal_Int32 nIndex = 0;
8715 
8716     // separator
8717     // note: can only be defined for the whole document, not per section
8718     m_pSerializer->startElementNS( XML_w, nItem,
8719             FSNS( XML_w, XML_id ), OString::number(nIndex++),
8720             FSNS( XML_w, XML_type ), "separator" );
8721     m_pSerializer->startElementNS(XML_w, XML_p);
8722 
8723     bool bSeparator = true;
8724     SwTwips nHeight(0);
8725     if (bFootnotes)
8726     {
8727         const SwPageFootnoteInfo& rFootnoteInfo = m_rExport.m_rDoc.GetPageDesc(0).GetFootnoteInfo();
8728         // Request separator only if both width and thickness are non-zero.
8729         bSeparator = rFootnoteInfo.GetLineStyle() != SvxBorderLineStyle::NONE
8730                   && rFootnoteInfo.GetLineWidth() > 0
8731                   && double(rFootnoteInfo.GetWidth()) > 0;
8732         nHeight = sw::FootnoteSeparatorHeight(m_rExport.m_rDoc, rFootnoteInfo);
8733 
8734         const IDocumentSettingAccess& rIDSA = m_rExport.m_rDoc.getIDocumentSettingAccess();
8735         if (rIDSA.get(DocumentSettingId::CONTINUOUS_ENDNOTES))
8736         {
8737             // Don't request separator if this is a Word-style separator, which is handled at a
8738             // layout level.
8739             nHeight = 0;
8740         }
8741     }
8742 
8743     WriteFootnoteSeparatorHeight(m_pSerializer, nHeight);
8744 
8745     m_pSerializer->startElementNS(XML_w, XML_r);
8746     if (bSeparator)
8747         m_pSerializer->singleElementNS(XML_w, XML_separator);
8748     m_pSerializer->endElementNS( XML_w, XML_r );
8749     m_pSerializer->endElementNS( XML_w, XML_p );
8750     m_pSerializer->endElementNS( XML_w, nItem );
8751 
8752     // separator
8753     m_pSerializer->startElementNS( XML_w, nItem,
8754             FSNS( XML_w, XML_id ), OString::number(nIndex++),
8755             FSNS( XML_w, XML_type ), "continuationSeparator" );
8756     m_pSerializer->startElementNS(XML_w, XML_p);
8757 
8758     WriteFootnoteSeparatorHeight(m_pSerializer, nHeight);
8759 
8760     m_pSerializer->startElementNS(XML_w, XML_r);
8761     if (bSeparator)
8762     {
8763         m_pSerializer->singleElementNS(XML_w, XML_continuationSeparator);
8764     }
8765     m_pSerializer->endElementNS( XML_w, XML_r );
8766     m_pSerializer->endElementNS( XML_w, XML_p );
8767     m_pSerializer->endElementNS( XML_w, nItem );
8768 
8769     // if new special ones are added, update also WriteFootnoteEndnotePr()
8770 
8771     // footnotes/endnotes themselves
8772     for ( const auto& rpItem : rVector )
8773     {
8774         m_footnoteEndnoteRefTag = bFootnotes ? XML_footnoteRef : XML_endnoteRef;
8775         m_footnoteCustomLabel = rpItem->GetNumStr();
8776 
8777         m_pSerializer->startElementNS(XML_w, nItem, FSNS(XML_w, XML_id), OString::number(nIndex));
8778 
8779         const SwNodeIndex* pIndex = rpItem->GetTextFootnote()->GetStartNode();
8780         m_rExport.WriteSpecialText( pIndex->GetIndex() + 1,
8781                 pIndex->GetNode().EndOfSectionIndex(),
8782                 bFootnotes? TXT_FTN: TXT_EDN );
8783 
8784         m_pSerializer->endElementNS( XML_w, nItem );
8785         ++nIndex;
8786     }
8787 
8788     m_pSerializer->endElementNS( XML_w, nBody );
8789 
8790 }
8791 
WriteFootnoteEndnotePr(::sax_fastparser::FSHelperPtr const & fs,int tag,const SwEndNoteInfo & info,int listtag)8792 void DocxAttributeOutput::WriteFootnoteEndnotePr( ::sax_fastparser::FSHelperPtr const & fs, int tag,
8793     const SwEndNoteInfo& info, int listtag )
8794 {
8795     fs->startElementNS(XML_w, tag);
8796 
8797     SwSectionFormats& rSections = m_rExport.m_rDoc.GetSections();
8798     if (!rSections.empty())
8799     {
8800         SwSectionFormat* pFormat = rSections[0];
8801         bool bEndnAtEnd = pFormat->GetEndAtTextEnd().IsAtEnd();
8802         if (bEndnAtEnd)
8803         {
8804             fs->singleElementNS(XML_w, XML_pos, FSNS(XML_w, XML_val), "sectEnd");
8805         }
8806     }
8807 
8808     OString aCustomFormat;
8809     OString fmt = lcl_ConvertNumberingType(info.m_aFormat.GetNumberingType(), nullptr, aCustomFormat);
8810     if (!fmt.isEmpty() && aCustomFormat.isEmpty())
8811         fs->singleElementNS(XML_w, XML_numFmt, FSNS(XML_w, XML_val), fmt);
8812     if( info.m_nFootnoteOffset != 0 )
8813         fs->singleElementNS( XML_w, XML_numStart, FSNS( XML_w, XML_val ),
8814             OString::number(info.m_nFootnoteOffset + 1) );
8815 
8816     const SwFootnoteInfo* pFootnoteInfo = dynamic_cast<const SwFootnoteInfo*>(&info);
8817     if( pFootnoteInfo )
8818     {
8819         switch( pFootnoteInfo->m_eNum )
8820         {
8821             case FTNNUM_PAGE:       fmt = "eachPage"_ostr; break;
8822             case FTNNUM_CHAPTER:    fmt = "eachSect"_ostr; break;
8823             default:                fmt.clear();      break;
8824         }
8825         if (!fmt.isEmpty())
8826             fs->singleElementNS(XML_w, XML_numRestart, FSNS(XML_w, XML_val), fmt);
8827     }
8828 
8829     if( listtag != 0 ) // we are writing to settings.xml, write also special footnote/endnote list
8830     { // there are currently only two hardcoded ones ( see FootnotesEndnotes())
8831         fs->singleElementNS(XML_w, listtag, FSNS(XML_w, XML_id), "0");
8832         fs->singleElementNS(XML_w, listtag, FSNS(XML_w, XML_id), "1");
8833     }
8834     fs->endElementNS( XML_w, tag );
8835 }
8836 
SectFootnoteEndnotePr()8837 void DocxAttributeOutput::SectFootnoteEndnotePr()
8838 {
8839     if( HasFootnotes())
8840         WriteFootnoteEndnotePr( m_pSerializer, XML_footnotePr, m_rExport.m_rDoc.GetFootnoteInfo(), 0 );
8841     if( HasEndnotes())
8842         WriteFootnoteEndnotePr( m_pSerializer, XML_endnotePr, m_rExport.m_rDoc.GetEndNoteInfo(), 0 );
8843 }
8844 
ParaLineSpacing_Impl(short nSpace,short nMulti)8845 void DocxAttributeOutput::ParaLineSpacing_Impl( short nSpace, short nMulti )
8846 {
8847     if ( nSpace < 0 )
8848     {
8849         AddToAttrList( m_pParagraphSpacingAttrList,
8850                 FSNS( XML_w, XML_lineRule ), "exact",
8851                 FSNS( XML_w, XML_line ), OString::number( -nSpace ) );
8852     }
8853     else if( nSpace > 0 && nMulti )
8854     {
8855         AddToAttrList( m_pParagraphSpacingAttrList,
8856                 FSNS( XML_w, XML_lineRule ), "auto",
8857                 FSNS( XML_w, XML_line ), OString::number( nSpace ) );
8858     }
8859     else
8860     {
8861         AddToAttrList( m_pParagraphSpacingAttrList,
8862                 FSNS( XML_w, XML_lineRule ), "atLeast",
8863                 FSNS( XML_w, XML_line ), OString::number( nSpace ) );
8864     }
8865 }
8866 
ParaAdjust(const SvxAdjustItem & rAdjust)8867 void DocxAttributeOutput::ParaAdjust( const SvxAdjustItem& rAdjust )
8868 {
8869     const char *pAdjustString;
8870 
8871     bool const bEcma = GetExport().GetFilter().getVersion() == oox::core::ECMA_376_1ST_EDITION;
8872 
8873     const SfxItemSet* pItems = GetExport().GetCurItemSet();
8874     const SvxFrameDirectionItem* rFrameDir = pItems?
8875         pItems->GetItem( RES_FRAMEDIR ) : nullptr;
8876 
8877     SvxFrameDirection nDir = SvxFrameDirection::Environment;
8878     if( rFrameDir != nullptr )
8879         nDir = rFrameDir->GetValue();
8880     if ( nDir == SvxFrameDirection::Environment )
8881         nDir = GetExport( ).GetDefaultFrameDirection( );
8882     bool bRtl = ( nDir == SvxFrameDirection::Horizontal_RL_TB );
8883 
8884     switch ( rAdjust.GetAdjust() )
8885     {
8886         case SvxAdjust::Left:
8887             if ( bEcma )
8888             {
8889                 if ( bRtl )
8890                     pAdjustString = "right";
8891                 else
8892                     pAdjustString = "left";
8893             }
8894             else if ( bRtl )
8895                 pAdjustString = "end";
8896             else
8897                 pAdjustString = "start";
8898             break;
8899         case SvxAdjust::Right:
8900             if ( bEcma )
8901             {
8902                 if ( bRtl )
8903                     pAdjustString = "left";
8904                 else
8905                     pAdjustString = "right";
8906             }
8907             else if ( bRtl )
8908                 pAdjustString = "start";
8909             else
8910                 pAdjustString = "end";
8911             break;
8912         case SvxAdjust::BlockLine:
8913         case SvxAdjust::Block:
8914             if (rAdjust.GetLastBlock() == SvxAdjust::Block)
8915                 pAdjustString = "distribute";
8916             else
8917                 pAdjustString = "both";
8918             break;
8919         case SvxAdjust::Center:
8920             pAdjustString = "center";
8921             break;
8922         default:
8923             return; // not supported attribute
8924     }
8925     m_pSerializer->singleElementNS(XML_w, XML_jc, FSNS(XML_w, XML_val), pAdjustString);
8926 }
8927 
ParaSplit(const SvxFormatSplitItem & rSplit)8928 void DocxAttributeOutput::ParaSplit( const SvxFormatSplitItem& rSplit )
8929 {
8930     if (rSplit.GetValue())
8931         m_pSerializer->singleElementNS(XML_w, XML_keepLines, FSNS(XML_w, XML_val), "false");
8932     else
8933         m_pSerializer->singleElementNS(XML_w, XML_keepLines);
8934 }
8935 
ParaWidows(const SvxWidowsItem & rWidows)8936 void DocxAttributeOutput::ParaWidows( const SvxWidowsItem& rWidows )
8937 {
8938     if (rWidows.GetValue())
8939         m_pSerializer->singleElementNS(XML_w, XML_widowControl);
8940     else
8941         m_pSerializer->singleElementNS(XML_w, XML_widowControl, FSNS(XML_w, XML_val), "false");
8942 }
8943 
impl_WriteTabElement(FSHelperPtr const & pSerializer,const SvxTabStop & rTab,tools::Long tabsOffset)8944 static void impl_WriteTabElement( FSHelperPtr const & pSerializer,
8945                                   const SvxTabStop& rTab, tools::Long tabsOffset )
8946 {
8947     rtl::Reference<FastAttributeList> pTabElementAttrList = FastSerializerHelper::createAttrList();
8948 
8949     switch (rTab.GetAdjustment())
8950     {
8951     case SvxTabAdjust::Right:
8952         pTabElementAttrList->add( FSNS( XML_w, XML_val ), "right" );
8953         break;
8954     case SvxTabAdjust::Decimal:
8955         pTabElementAttrList->add( FSNS( XML_w, XML_val ), "decimal" );
8956         break;
8957     case SvxTabAdjust::Center:
8958         pTabElementAttrList->add( FSNS( XML_w, XML_val ), "center" );
8959         break;
8960     case SvxTabAdjust::Default:
8961     case SvxTabAdjust::Left:
8962     default:
8963         pTabElementAttrList->add( FSNS( XML_w, XML_val ), "left" );
8964         break;
8965     }
8966 
8967     // Write position according to used offset of the whole paragraph.
8968     // In DOCX, w:pos specifies the position of the current custom tab stop with respect to the current page margins.
8969     // But in ODT, zero position could be page margins or paragraph indent according to used settings.
8970     // This is handled outside of this method and provided for us in tabsOffset parameter.
8971     pTabElementAttrList->add( FSNS( XML_w, XML_pos ), OString::number( rTab.GetTabPos() + tabsOffset ) );
8972 
8973     sal_Unicode cFillChar = rTab.GetFill();
8974 
8975     if ('.' == cFillChar )
8976         pTabElementAttrList->add( FSNS( XML_w, XML_leader ), "dot" );
8977     else if ( '-' == cFillChar )
8978         pTabElementAttrList->add( FSNS( XML_w, XML_leader ), "hyphen" );
8979     else if ( u'\x00B7' == cFillChar ) // middle dot
8980         pTabElementAttrList->add( FSNS( XML_w, XML_leader ), "middleDot" );
8981     else if ( '_' == cFillChar )
8982         pTabElementAttrList->add( FSNS( XML_w, XML_leader ), "underscore" );
8983     else
8984         pTabElementAttrList->add( FSNS( XML_w, XML_leader ), "none" );
8985 
8986     pSerializer->singleElementNS(XML_w, XML_tab, pTabElementAttrList);
8987 }
8988 
ParaTabStop(const SvxTabStopItem & rTabStop)8989 void DocxAttributeOutput::ParaTabStop( const SvxTabStopItem& rTabStop )
8990 {
8991     const SvxTabStopItem* pInheritedTabs = nullptr;
8992     if ( GetExport().m_pStyAttr )
8993         pInheritedTabs = GetExport().m_pStyAttr->GetItem<SvxTabStopItem>(RES_PARATR_TABSTOP);
8994     else if ( GetExport().m_pCurrentStyle && GetExport().m_pCurrentStyle->DerivedFrom() )
8995         pInheritedTabs = GetExport().m_pCurrentStyle->DerivedFrom()->GetAttrSet().GetItem<SvxTabStopItem>(RES_PARATR_TABSTOP);
8996     const sal_uInt16 nInheritedTabCount = pInheritedTabs ? pInheritedTabs->Count() : 0;
8997     const sal_uInt16 nCount = rTabStop.Count();
8998 
8999     // <w:tabs> must contain at least one <w:tab>, so don't write it empty
9000     if ( !nCount && !nInheritedTabCount )
9001         return;
9002     if( nCount == 1 && rTabStop[ 0 ].GetAdjustment() == SvxTabAdjust::Default )
9003     {
9004         GetExport().setDefaultTabStop( rTabStop[ 0 ].GetTabPos());
9005         return;
9006     }
9007 
9008     // do not output inherited tabs twice (inside styles and inside inline properties)
9009     if ( nCount == nInheritedTabCount && nCount > 0 )
9010     {
9011         if ( *pInheritedTabs == rTabStop )
9012             return;
9013     }
9014 
9015     m_pSerializer->startElementNS(XML_w, XML_tabs);
9016 
9017     // Get offset for tabs
9018     // In DOCX, w:pos specifies the position of the current custom tab stop with respect to the current page margins.
9019     // But in ODT, zero position could be page margins or paragraph indent according to used settings.
9020     tools::Long tabsOffset = m_rExport.GetParaTabStopOffset();
9021 
9022     // clear unused inherited tabs - otherwise the style will add them back in
9023     sal_Int32 nCurrTab = 0;
9024     for ( sal_uInt16 i = 0; i < nInheritedTabCount; ++i )
9025     {
9026         while ( nCurrTab < nCount && rTabStop[nCurrTab] < pInheritedTabs->At(i) )
9027             ++nCurrTab;
9028 
9029         if ( nCurrTab == nCount || pInheritedTabs->At(i) < rTabStop[nCurrTab] )
9030         {
9031             m_pSerializer->singleElementNS( XML_w, XML_tab,
9032                 FSNS( XML_w, XML_val ), "clear",
9033                 FSNS( XML_w, XML_pos ), OString::number(pInheritedTabs->At(i).GetTabPos()) );
9034         }
9035     }
9036 
9037     for (sal_uInt16 i = 0; i < nCount; i++ )
9038     {
9039         if( rTabStop[i].GetAdjustment() != SvxTabAdjust::Default )
9040             impl_WriteTabElement( m_pSerializer, rTabStop[i], tabsOffset );
9041         else
9042             GetExport().setDefaultTabStop( rTabStop[i].GetTabPos());
9043     }
9044 
9045     m_pSerializer->endElementNS( XML_w, XML_tabs );
9046 }
9047 
ParaHyphenZone(const SvxHyphenZoneItem & rHyphenZone)9048 void DocxAttributeOutput::ParaHyphenZone( const SvxHyphenZoneItem& rHyphenZone )
9049 {
9050     m_pSerializer->singleElementNS( XML_w, XML_suppressAutoHyphens,
9051             FSNS( XML_w, XML_val ), OString::boolean( !rHyphenZone.IsHyphen() ) );
9052 }
9053 
ParaNumRule_Impl(const SwTextNode * pTextNd,sal_Int32 nLvl,sal_Int32 nNumId)9054 void DocxAttributeOutput::ParaNumRule_Impl( const SwTextNode* pTextNd, sal_Int32 nLvl, sal_Int32 nNumId )
9055 {
9056     if ( USHRT_MAX == nNumId )
9057         return;
9058 
9059     // LibreOffice is not very flexible with "Outline Numbering" (aka "Outline" numbering style).
9060     // Only ONE numbering rule ("Outline") can be associated with a style-assigned-listLevel,
9061     // and no other style is able to inherit these numId/nLvl settings - only text nodes can.
9062     // So listLevel only exists in paragraph properties EXCEPT for up to ten styles that have been
9063     // assigned to one of these special Chapter Numbering listlevels (by default Heading 1-10).
9064     const sal_Int32 nTableSize = m_rExport.m_pUsedNumTable ? m_rExport.m_pUsedNumTable->size() : 0;
9065     const SwNumRule* pRule = nNumId > 0 && nNumId <= nTableSize ? (*m_rExport.m_pUsedNumTable)[nNumId-1] : nullptr;
9066     const SwTextFormatColl* pColl = pTextNd ? pTextNd->GetTextColl() : nullptr;
9067     // Do not duplicate numbering that is inherited from the (Chapter numbering) style
9068     // (since on import we duplicate style numbering/listlevel to the paragraph).
9069     if (pColl && pColl->IsAssignedToListLevelOfOutlineStyle()
9070         && nLvl == pColl->GetAssignedOutlineStyleLevel() && pRule && pRule->IsOutlineRule())
9071     {
9072         // By definition of how LO is implemented, assignToListLevel is only possible
9073         // when the style is also using OutlineRule for numbering. Adjust logic if that changes.
9074         assert(pRule->GetName() == pColl->GetNumRule(true).GetValue());
9075         return;
9076     }
9077 
9078     m_pSerializer->startElementNS(XML_w, XML_numPr);
9079     m_pSerializer->singleElementNS(XML_w, XML_ilvl, FSNS(XML_w, XML_val), OString::number(nLvl));
9080     m_pSerializer->singleElementNS(XML_w, XML_numId, FSNS(XML_w, XML_val), OString::number(nNumId));
9081     m_pSerializer->endElementNS(XML_w, XML_numPr);
9082 }
9083 
ParaScriptSpace(const SfxBoolItem & rScriptSpace)9084 void DocxAttributeOutput::ParaScriptSpace( const SfxBoolItem& rScriptSpace )
9085 {
9086     m_pSerializer->singleElementNS( XML_w, XML_autoSpaceDE,
9087            FSNS( XML_w, XML_val ), OString::boolean( rScriptSpace.GetValue() ) );
9088 }
9089 
ParaHangingPunctuation(const SfxBoolItem & rItem)9090 void DocxAttributeOutput::ParaHangingPunctuation( const SfxBoolItem& rItem )
9091 {
9092     m_pSerializer->singleElementNS( XML_w, XML_overflowPunct,
9093            FSNS( XML_w, XML_val ), OString::boolean( rItem.GetValue() ) );
9094 }
9095 
ParaForbiddenRules(const SfxBoolItem & rItem)9096 void DocxAttributeOutput::ParaForbiddenRules( const SfxBoolItem& rItem )
9097 {
9098     m_pSerializer->singleElementNS( XML_w, XML_kinsoku,
9099            FSNS( XML_w, XML_val ), OString::boolean( rItem.GetValue() ) );
9100 }
9101 
ParaVerticalAlign(const SvxParaVertAlignItem & rAlign)9102 void DocxAttributeOutput::ParaVerticalAlign( const SvxParaVertAlignItem& rAlign )
9103 {
9104     const char *pAlignString;
9105 
9106     switch ( rAlign.GetValue() )
9107     {
9108         case SvxParaVertAlignItem::Align::Baseline:
9109             pAlignString = "baseline";
9110             break;
9111         case SvxParaVertAlignItem::Align::Top:
9112             pAlignString = "top";
9113             break;
9114         case SvxParaVertAlignItem::Align::Center:
9115             pAlignString = "center";
9116             break;
9117         case SvxParaVertAlignItem::Align::Bottom:
9118             pAlignString = "bottom";
9119             break;
9120         case SvxParaVertAlignItem::Align::Automatic:
9121             pAlignString = "auto";
9122             break;
9123         default:
9124             return; // not supported attribute
9125     }
9126     m_pSerializer->singleElementNS(XML_w, XML_textAlignment, FSNS(XML_w, XML_val), pAlignString);
9127 }
9128 
ParaSnapToGrid(const SvxParaGridItem & rGrid)9129 void DocxAttributeOutput::ParaSnapToGrid( const SvxParaGridItem& rGrid )
9130 {
9131     m_pSerializer->singleElementNS( XML_w, XML_snapToGrid,
9132             FSNS( XML_w, XML_val ), OString::boolean( rGrid.GetValue() ) );
9133 }
9134 
FormatFrameSize(const SwFormatFrameSize & rSize)9135 void DocxAttributeOutput::FormatFrameSize( const SwFormatFrameSize& rSize )
9136 {
9137     if (m_rExport.SdrExporter().getTextFrameSyntax() && m_rExport.SdrExporter().getFlyFrameSize())
9138     {
9139         const Size* pSize = m_rExport.SdrExporter().getFlyFrameSize();
9140         m_rExport.SdrExporter().getTextFrameStyle().append(";width:" + OString::number(double(pSize->Width()) / 20));
9141         m_rExport.SdrExporter().getTextFrameStyle().append("pt;height:" + OString::number(double(pSize->Height()) / 20) + "pt");
9142     }
9143     else if (m_rExport.SdrExporter().getDMLTextFrameSyntax())
9144     {
9145     }
9146     else if ( m_rExport.m_bOutFlyFrameAttrs )
9147     {
9148         if ( rSize.GetWidth() && rSize.GetWidthSizeType() == SwFrameSize::Fixed )
9149             AddToAttrList( m_rExport.SdrExporter().getFlyAttrList(),
9150                     FSNS( XML_w, XML_w ), OString::number( rSize.GetWidth( ) ) );
9151 
9152         if ( rSize.GetHeight() )
9153         {
9154             std::string_view sRule( "exact" );
9155             if ( rSize.GetHeightSizeType() == SwFrameSize::Minimum )
9156                 sRule = "atLeast";
9157             AddToAttrList( m_rExport.SdrExporter().getFlyAttrList(),
9158                     FSNS( XML_w, XML_hRule ), sRule,
9159                     FSNS( XML_w, XML_h ), OString::number( rSize.GetHeight( ) ) );
9160         }
9161     }
9162     else if ( m_rExport.m_bOutPageDescs )
9163     {
9164         rtl::Reference<FastAttributeList> attrList = FastSerializerHelper::createAttrList( );
9165         if ( m_rExport.m_pCurrentPageDesc->GetLandscape( ) )
9166             attrList->add( FSNS( XML_w, XML_orient ), "landscape" );
9167 
9168         attrList->add( FSNS( XML_w, XML_w ), OString::number( rSize.GetWidth( ) ) );
9169         attrList->add( FSNS( XML_w, XML_h ), OString::number( rSize.GetHeight( ) ) );
9170 
9171         m_pSerializer->singleElementNS( XML_w, XML_pgSz, attrList );
9172     }
9173 }
9174 
FormatPaperBin(const SvxPaperBinItem & rPaperBin)9175 void DocxAttributeOutput::FormatPaperBin(const SvxPaperBinItem& rPaperBin)
9176 {
9177     sal_Int8 nPaperBin = rPaperBin.GetValue();
9178     rtl::Reference<FastAttributeList> attrList = FastSerializerHelper::createAttrList( );
9179     SfxPrinter* pPrinter = m_rExport.m_rDoc.getIDocumentDeviceAccess().getPrinter(true);
9180     sal_Int16 nPaperSource = pPrinter->GetSourceIndexByPaperBin(nPaperBin);
9181     attrList->add( FSNS( XML_w, XML_first ), OString::number(nPaperSource) );
9182     attrList->add( FSNS( XML_w, XML_other ), OString::number(nPaperSource) );
9183     m_pSerializer->singleElementNS( XML_w, XML_paperSrc, attrList );
9184 }
9185 
FormatFirstLineIndent(SvxFirstLineIndentItem const & rFirstLine)9186 void DocxAttributeOutput::FormatFirstLineIndent(SvxFirstLineIndentItem const& rFirstLine)
9187 {
9188     sal_Int32 const nFirstLineAdjustment(rFirstLine.GetTextFirstLineOffset());
9189     if (nFirstLineAdjustment > 0)
9190     {
9191         AddToAttrList(m_pLRSpaceAttrList, FSNS(XML_w, XML_firstLine),
9192                 OString::number(nFirstLineAdjustment));
9193     }
9194     else
9195     {
9196         AddToAttrList(m_pLRSpaceAttrList, FSNS(XML_w, XML_hanging),
9197                 OString::number(- nFirstLineAdjustment));
9198     }
9199 }
9200 
FormatTextLeftMargin(SvxTextLeftMarginItem const & rTextLeftMargin)9201 void DocxAttributeOutput::FormatTextLeftMargin(SvxTextLeftMarginItem const& rTextLeftMargin)
9202 {
9203     SvxTextLeftMarginItem const* pTextLeftMargin(&rTextLeftMargin);
9204     ::std::optional<SvxTextLeftMarginItem> oCopy;
9205     if (dynamic_cast<SwContentNode const*>(GetExport().m_pOutFormatNode) != nullptr)
9206     {
9207         auto pTextNd(static_cast<SwTextNode const*>(GetExport().m_pOutFormatNode));
9208         // WW doesn't have a concept of a paragraph that's in a list but not
9209         // counted in the list - see AttributeOutputBase::ParaNumRule()
9210         // forcing non-existent numId="0" in this case.
9211         // This means WW won't apply the indents from the numbering,
9212         // so try to add them as paragraph properties here.
9213         if (!pTextNd->IsCountedInList())
9214         {
9215             SfxItemSetFixed<RES_MARGIN_TEXTLEFT, RES_MARGIN_TEXTLEFT> temp(m_rExport.m_rDoc.GetAttrPool());
9216             pTextNd->GetParaAttr(temp, 0, 0, false, true, true, nullptr);
9217             if (auto *const pItem = temp.GetItem(RES_MARGIN_TEXTLEFT))
9218             {
9219                 oCopy.emplace(*pItem);
9220                 pTextLeftMargin = &*oCopy;
9221             }
9222         }
9223     }
9224     bool const bEcma1st(m_rExport.GetFilter().getVersion() == oox::core::ECMA_376_1ST_EDITION);
9225     AddToAttrList(m_pLRSpaceAttrList,
9226         FSNS(XML_w, (bEcma1st ? XML_left : XML_start)),
9227         OString::number(pTextLeftMargin->GetTextLeft()));
9228 }
9229 
FormatRightMargin(SvxRightMarginItem const & rRightMargin)9230 void DocxAttributeOutput::FormatRightMargin(SvxRightMarginItem const& rRightMargin)
9231 {
9232     // (paragraph case, this will be an else branch once others are converted)
9233 #if 0
9234     else
9235 #endif
9236     {
9237         bool const bEcma1st(m_rExport.GetFilter().getVersion() == oox::core::ECMA_376_1ST_EDITION);
9238         AddToAttrList(m_pLRSpaceAttrList,
9239             FSNS(XML_w, (bEcma1st ? XML_right : XML_end)),
9240             OString::number(rRightMargin.GetRight()));
9241     }
9242 }
9243 
FormatLRSpace(const SvxLRSpaceItem & rLRSpace)9244 void DocxAttributeOutput::FormatLRSpace( const SvxLRSpaceItem& rLRSpace )
9245 {
9246     bool const bEcma = m_rExport.GetFilter().getVersion() == oox::core::ECMA_376_1ST_EDITION;
9247     if (m_rExport.SdrExporter().getTextFrameSyntax())
9248     {
9249         m_rExport.SdrExporter().getTextFrameStyle().append(";mso-wrap-distance-left:" + OString::number(double(rLRSpace.GetLeft()) / 20) + "pt");
9250         m_rExport.SdrExporter().getTextFrameStyle().append(";mso-wrap-distance-right:" + OString::number(double(rLRSpace.GetRight()) / 20) + "pt");
9251     }
9252     else if (m_rExport.SdrExporter().getDMLTextFrameSyntax())
9253     {
9254     }
9255     else if ( m_rExport.m_bOutFlyFrameAttrs )
9256     {
9257         AddToAttrList( m_rExport.SdrExporter().getFlyAttrList(), FSNS( XML_w, XML_hSpace ),
9258                 OString::number(
9259                     ( rLRSpace.GetLeft() + rLRSpace.GetRight() ) / 2 ) );
9260     }
9261     else if ( m_rExport.m_bOutPageDescs )
9262     {
9263         m_pageMargins.nLeft = 0;
9264         m_pageMargins.nRight = 0;
9265 
9266         const SvxBoxItem* pBoxItem = m_rExport.HasItem(RES_BOX);
9267         if (pBoxItem)
9268         {
9269             m_pageMargins.nLeft = pBoxItem->CalcLineSpace( SvxBoxItemLine::LEFT, /*bEvenIfNoLine*/true );
9270             m_pageMargins.nRight = pBoxItem->CalcLineSpace( SvxBoxItemLine::RIGHT, /*bEvenIfNoLine*/true );
9271         }
9272 
9273         m_pageMargins.nLeft += sal::static_int_cast<sal_uInt16>(rLRSpace.GetLeft());
9274         m_pageMargins.nRight += sal::static_int_cast<sal_uInt16>(rLRSpace.GetRight());
9275         // if page layout is 'left' then left/right margin need to be exchanged
9276         // as it is exported as mirrored layout starting with even page
9277         const WW8_SepInfo *pSectionInfo = m_rExport.Sections().CurrentSectionInfo();
9278         if (pSectionInfo->pPageDesc &&
9279             m_rExport.isMirroredMargin() &&
9280             ((pSectionInfo->pPageDesc->ReadUseOn() & UseOnPage::All) == UseOnPage::Left))
9281         {
9282             sal_uInt16 nLeft = m_pageMargins.nLeft;
9283             m_pageMargins.nLeft = m_pageMargins.nRight;
9284             m_pageMargins.nRight = nLeft;
9285         }
9286         sal_uInt16 nGutter = rLRSpace.GetGutterMargin();
9287 
9288         AddToAttrList( m_pSectionSpacingAttrList,
9289                 FSNS( XML_w, XML_left ), OString::number( m_pageMargins.nLeft ),
9290                 FSNS( XML_w, XML_right ), OString::number( m_pageMargins.nRight ),
9291                 FSNS( XML_w, XML_gutter ), OString::number( nGutter ) );
9292     }
9293     else
9294     {
9295         // note: this is not possible for SwTextNode but is for EditEngine!
9296         SvxLRSpaceItem const* pLRSpace(&rLRSpace);
9297         ::std::optional<SvxLRSpaceItem> oLRSpace;
9298         assert(dynamic_cast<SwContentNode const*>(GetExport().m_pOutFormatNode) == nullptr);
9299         rtl::Reference<FastAttributeList> pLRSpaceAttrList = FastSerializerHelper::createAttrList();
9300         if ((0 != pLRSpace->GetTextLeft()) || (pLRSpace->IsExplicitZeroMarginValLeft()))
9301         {
9302             pLRSpaceAttrList->add( FSNS(XML_w, (bEcma ? XML_left : XML_start)), OString::number(pLRSpace->GetTextLeft()) );
9303         }
9304         if ((0 != pLRSpace->GetRight()) || (pLRSpace->IsExplicitZeroMarginValRight()))
9305         {
9306             pLRSpaceAttrList->add( FSNS(XML_w, (bEcma ? XML_right : XML_end)), OString::number(pLRSpace->GetRight()) );
9307         }
9308         sal_Int32 const nFirstLineAdjustment = pLRSpace->GetTextFirstLineOffset();
9309         if (nFirstLineAdjustment > 0)
9310             pLRSpaceAttrList->add( FSNS( XML_w, XML_firstLine ), OString::number( nFirstLineAdjustment ) );
9311         else
9312             pLRSpaceAttrList->add( FSNS( XML_w, XML_hanging ), OString::number( - nFirstLineAdjustment ) );
9313         m_pSerializer->singleElementNS( XML_w, XML_ind, pLRSpaceAttrList );
9314     }
9315 }
9316 
FormatULSpace(const SvxULSpaceItem & rULSpace)9317 void DocxAttributeOutput::FormatULSpace( const SvxULSpaceItem& rULSpace )
9318 {
9319 
9320     if (m_rExport.SdrExporter().getTextFrameSyntax())
9321     {
9322         m_rExport.SdrExporter().getTextFrameStyle().append(";mso-wrap-distance-top:" + OString::number(double(rULSpace.GetUpper()) / 20) + "pt");
9323         m_rExport.SdrExporter().getTextFrameStyle().append(";mso-wrap-distance-bottom:" + OString::number(double(rULSpace.GetLower()) / 20) + "pt");
9324     }
9325     else if (m_rExport.SdrExporter().getDMLTextFrameSyntax())
9326     {
9327     }
9328     else if ( m_rExport.m_bOutFlyFrameAttrs )
9329     {
9330         AddToAttrList( m_rExport.SdrExporter().getFlyAttrList(), FSNS( XML_w, XML_vSpace ),
9331                 OString::number(
9332                     ( rULSpace.GetLower() + rULSpace.GetUpper() ) / 2 ) );
9333     }
9334     else if (m_rExport.m_bOutPageDescs )
9335     {
9336         OSL_ENSURE( m_rExport.GetCurItemSet(), "Impossible" );
9337         if ( !m_rExport.GetCurItemSet() )
9338             return;
9339 
9340         HdFtDistanceGlue aDistances( *m_rExport.GetCurItemSet() );
9341 
9342         sal_Int32 nHeader = 0;
9343         if ( aDistances.HasHeader() )
9344             nHeader = sal_Int32( aDistances.m_DyaHdrTop );
9345         else if (m_rExport.m_pFirstPageFormat)
9346         {
9347             HdFtDistanceGlue aFirstPageDistances(m_rExport.m_pFirstPageFormat->GetAttrSet());
9348             if (aFirstPageDistances.HasHeader())
9349             {
9350                 // The follow page style has no header, but the first page style has. In Word terms,
9351                 // this means that the header margin of "the" section is coming from the first page
9352                 // style.
9353                 nHeader = sal_Int32(aFirstPageDistances.m_DyaHdrTop);
9354             }
9355         }
9356 
9357         // Page top
9358         m_pageMargins.nTop = aDistances.m_DyaTop;
9359 
9360         sal_Int32 nFooter = 0;
9361         if ( aDistances.HasFooter() )
9362             nFooter = sal_Int32( aDistances.m_DyaHdrBottom );
9363         else if (m_rExport.m_pFirstPageFormat)
9364         {
9365             HdFtDistanceGlue aFirstPageDistances(m_rExport.m_pFirstPageFormat->GetAttrSet());
9366             if (aFirstPageDistances.HasFooter())
9367             {
9368                 // The follow page style has no footer, but the first page style has. In Word terms,
9369                 // this means that the footer margin of "the" section is coming from the first page
9370                 // style.
9371                 nFooter = sal_Int32(aFirstPageDistances.m_DyaHdrBottom);
9372             }
9373         }
9374 
9375         // Page Bottom
9376         m_pageMargins.nBottom = aDistances.m_DyaBottom;
9377 
9378         AddToAttrList( m_pSectionSpacingAttrList,
9379                 FSNS( XML_w, XML_header ), OString::number( nHeader ),
9380                 FSNS( XML_w, XML_top ), OString::number( m_pageMargins.nTop ),
9381                 FSNS( XML_w, XML_footer ), OString::number( nFooter ),
9382                 FSNS( XML_w, XML_bottom ), OString::number( m_pageMargins.nBottom ) );
9383     }
9384     else
9385     {
9386         SAL_INFO("sw.ww8", "DocxAttributeOutput::FormatULSpace: setting spacing" << rULSpace.GetUpper() );
9387         // check if before auto spacing was set during import and spacing we get from actual object is same
9388         // that we set in import. If yes just write beforeAutoSpacing tag.
9389         if (m_bParaBeforeAutoSpacing && m_nParaBeforeSpacing == rULSpace.GetUpper())
9390         {
9391             AddToAttrList( m_pParagraphSpacingAttrList,
9392                     FSNS( XML_w, XML_beforeAutospacing ), "1" );
9393         }
9394         else if (m_bParaBeforeAutoSpacing && m_nParaBeforeSpacing == -1)
9395         {
9396             AddToAttrList( m_pParagraphSpacingAttrList,
9397                     FSNS( XML_w, XML_beforeAutospacing ), "0",
9398                     FSNS( XML_w, XML_before ), OString::number( rULSpace.GetUpper() ) );
9399         }
9400         else
9401         {
9402             AddToAttrList( m_pParagraphSpacingAttrList,
9403                     FSNS( XML_w, XML_before ), OString::number( rULSpace.GetUpper() ) );
9404         }
9405         m_bParaBeforeAutoSpacing = false;
9406         // check if after auto spacing was set during import and spacing we get from actual object is same
9407         // that we set in import. If yes just write afterAutoSpacing tag.
9408         if (m_bParaAfterAutoSpacing && m_nParaAfterSpacing == rULSpace.GetLower())
9409         {
9410             AddToAttrList( m_pParagraphSpacingAttrList,
9411                     FSNS( XML_w, XML_afterAutospacing ), "1" );
9412         }
9413         else if (m_bParaAfterAutoSpacing && m_nParaAfterSpacing == -1)
9414         {
9415             AddToAttrList( m_pParagraphSpacingAttrList,
9416                     FSNS( XML_w, XML_afterAutospacing ), "0",
9417                     FSNS( XML_w, XML_after ), OString::number( rULSpace.GetLower()) );
9418         }
9419         else
9420         {
9421             AddToAttrList( m_pParagraphSpacingAttrList,
9422                     FSNS( XML_w, XML_after ), OString::number( rULSpace.GetLower()) );
9423         }
9424         m_bParaAfterAutoSpacing = false;
9425 
9426         if (rULSpace.GetContext())
9427             m_pSerializer->singleElementNS(XML_w, XML_contextualSpacing);
9428         else
9429         {
9430             // Write out Contextual Spacing = false if it would have inherited a true.
9431             const SvxULSpaceItem* pInherited = nullptr;
9432             if (auto pNd = dynamic_cast<const SwContentNode*>(m_rExport.m_pOutFormatNode)) //paragraph
9433                 pInherited = &static_cast<SwTextFormatColl&>(pNd->GetAnyFormatColl()).GetAttrSet().GetULSpace();
9434             else if (m_rExport.m_bStyDef && m_rExport.m_pCurrentStyle && m_rExport.m_pCurrentStyle->DerivedFrom()) //style
9435                 pInherited = &m_rExport.m_pCurrentStyle->DerivedFrom()->GetULSpace();
9436 
9437             if (pInherited && pInherited->GetContext())
9438                 m_pSerializer->singleElementNS(XML_w, XML_contextualSpacing, FSNS(XML_w, XML_val), "false");
9439         }
9440     }
9441 }
9442 
9443 namespace docx {
9444 
SurroundToVMLWrap(SwFormatSurround const & rSurround)9445 rtl::Reference<FastAttributeList> SurroundToVMLWrap(SwFormatSurround const& rSurround)
9446 {
9447     std::string_view sType;
9448     std::string_view sSide;
9449     switch (rSurround.GetSurround())
9450     {
9451         case css::text::WrapTextMode_NONE:
9452             sType = "topAndBottom";
9453             break;
9454         case css::text::WrapTextMode_PARALLEL:
9455             sType = "square";
9456             break;
9457         case css::text::WrapTextMode_DYNAMIC:
9458             sType = "square";
9459             sSide = "largest";
9460             break;
9461         case css::text::WrapTextMode_LEFT:
9462             sType = "square";
9463             sSide = "left";
9464             break;
9465         case css::text::WrapTextMode_RIGHT:
9466             sType = "square";
9467             sSide = "right";
9468             break;
9469         case css::text::WrapTextMode_THROUGH:
9470             /* empty type and side means through */
9471         default:
9472             sType = "none";
9473             break;
9474     }
9475     rtl::Reference<FastAttributeList> pAttrList;
9476     if (!sType.empty())
9477         DocxAttributeOutput::AddToAttrList(pAttrList, XML_type, sType);
9478     if (!sSide.empty())
9479         DocxAttributeOutput::AddToAttrList(pAttrList, XML_side, sSide);
9480     return pAttrList;
9481 }
9482 
9483 } // namespace docx
9484 
FormatSurround(const SwFormatSurround & rSurround)9485 void DocxAttributeOutput::FormatSurround( const SwFormatSurround& rSurround )
9486 {
9487     if (m_rExport.SdrExporter().getTextFrameSyntax())
9488     {
9489         rtl::Reference<FastAttributeList> pAttrList(docx::SurroundToVMLWrap(rSurround));
9490         if (pAttrList)
9491         {
9492             m_rExport.SdrExporter().setFlyWrapAttrList(pAttrList);
9493         }
9494     }
9495     else if (m_rExport.SdrExporter().getDMLTextFrameSyntax())
9496     {
9497     }
9498     else if ( m_rExport.m_bOutFlyFrameAttrs )
9499     {
9500         std::string_view sWrap;
9501         switch ( rSurround.GetSurround( ) )
9502         {
9503             case css::text::WrapTextMode_NONE:
9504                 sWrap = "none";
9505                 break;
9506             case css::text::WrapTextMode_THROUGH:
9507                 sWrap = "through";
9508                 break;
9509             case css::text::WrapTextMode_DYNAMIC:
9510             case css::text::WrapTextMode_PARALLEL:
9511             case css::text::WrapTextMode_LEFT:
9512             case css::text::WrapTextMode_RIGHT:
9513             default:
9514                 sWrap = "around";
9515         }
9516 
9517         AddToAttrList( m_rExport.SdrExporter().getFlyAttrList(), FSNS( XML_w, XML_wrap ), sWrap );
9518     }
9519 }
9520 
FormatVertOrientation(const SwFormatVertOrient & rFlyVert)9521 void DocxAttributeOutput::FormatVertOrientation( const SwFormatVertOrient& rFlyVert )
9522 {
9523     OString sAlign   = convertToOOXMLVertOrient( rFlyVert.GetVertOrient() );
9524     OString sVAnchor = convertToOOXMLVertOrientRel( rFlyVert.GetRelationOrient() );
9525 
9526     if (m_rExport.SdrExporter().getTextFrameSyntax())
9527     {
9528         m_rExport.SdrExporter().getTextFrameStyle().append(";margin-top:" + OString::number(double(rFlyVert.GetPos()) / 20) + "pt");
9529         if ( !sAlign.isEmpty() )
9530             m_rExport.SdrExporter().getTextFrameStyle().append(";mso-position-vertical:" + sAlign);
9531         m_rExport.SdrExporter().getTextFrameStyle().append(";mso-position-vertical-relative:" + sVAnchor);
9532     }
9533     else if (m_rExport.SdrExporter().getDMLTextFrameSyntax())
9534     {
9535     }
9536     else if ( m_rExport.m_bOutFlyFrameAttrs )
9537     {
9538         if ( !sAlign.isEmpty() )
9539             AddToAttrList( m_rExport.SdrExporter().getFlyAttrList(), FSNS( XML_w, XML_yAlign ), sAlign );
9540         else
9541             AddToAttrList( m_rExport.SdrExporter().getFlyAttrList(), FSNS( XML_w, XML_y ),
9542                 OString::number( rFlyVert.GetPos() ) );
9543         AddToAttrList( m_rExport.SdrExporter().getFlyAttrList(), FSNS( XML_w, XML_vAnchor ), sVAnchor );
9544     }
9545 }
9546 
FormatHorizOrientation(const SwFormatHoriOrient & rFlyHori)9547 void DocxAttributeOutput::FormatHorizOrientation( const SwFormatHoriOrient& rFlyHori )
9548 {
9549     OString sAlign   = convertToOOXMLHoriOrient( rFlyHori.GetHoriOrient(), rFlyHori.IsPosToggle() );
9550     OString sHAnchor = convertToOOXMLHoriOrientRel( rFlyHori.GetRelationOrient() );
9551 
9552     if (m_rExport.SdrExporter().getTextFrameSyntax())
9553     {
9554         m_rExport.SdrExporter().getTextFrameStyle().append(";margin-left:" + OString::number(double(rFlyHori.GetPos()) / 20) + "pt");
9555         if ( !sAlign.isEmpty() )
9556             m_rExport.SdrExporter().getTextFrameStyle().append(";mso-position-horizontal:" + sAlign);
9557         m_rExport.SdrExporter().getTextFrameStyle().append(";mso-position-horizontal-relative:" + sHAnchor);
9558     }
9559     else if (m_rExport.SdrExporter().getDMLTextFrameSyntax())
9560     {
9561     }
9562     else if ( m_rExport.m_bOutFlyFrameAttrs )
9563     {
9564         if ( !sAlign.isEmpty() )
9565             AddToAttrList( m_rExport.SdrExporter().getFlyAttrList(), FSNS( XML_w, XML_xAlign ), sAlign );
9566         else
9567             AddToAttrList( m_rExport.SdrExporter().getFlyAttrList(), FSNS( XML_w, XML_x ),
9568                 OString::number( rFlyHori.GetPos() ) );
9569         AddToAttrList( m_rExport.SdrExporter().getFlyAttrList(), FSNS( XML_w, XML_hAnchor ), sHAnchor );
9570     }
9571 }
9572 
FormatAnchor(const SwFormatAnchor &)9573 void DocxAttributeOutput::FormatAnchor( const SwFormatAnchor& )
9574 {
9575     // Fly frames: anchors here aren't matching the anchors in docx
9576 }
9577 
lcl_getDmlAlpha(const SvxBrushItem & rBrush)9578 static std::optional<sal_Int32> lcl_getDmlAlpha(const SvxBrushItem& rBrush)
9579 {
9580     std::optional<sal_Int32> oRet;
9581     sal_Int32 nTransparency = 255 - rBrush.GetColor().GetAlpha();
9582     if (nTransparency)
9583     {
9584         // Convert transparency to percent
9585         sal_Int8 nTransparencyPercent = SvxBrushItem::TransparencyToPercent(nTransparency);
9586 
9587         // Calculate alpha value
9588         // Consider oox/source/drawingml/color.cxx : getTransparency() function.
9589         sal_Int32 nAlpha = ::oox::drawingml::MAX_PERCENT - ( ::oox::drawingml::PER_PERCENT * nTransparencyPercent );
9590         oRet = nAlpha;
9591     }
9592     return oRet;
9593 }
9594 
FormatBackground(const SvxBrushItem & rBrush)9595 void DocxAttributeOutput::FormatBackground( const SvxBrushItem& rBrush )
9596 {
9597     const Color aColor = rBrush.GetColor();
9598     model::ComplexColor const& rComplexColor = rBrush.getComplexColor();
9599     OString sColor = msfilter::util::ConvertColor( aColor.GetRGBColor() );
9600     std::optional<sal_Int32> oAlpha = lcl_getDmlAlpha(rBrush);
9601     if (m_rExport.SdrExporter().getTextFrameSyntax())
9602     {
9603         // Handle 'Opacity'
9604         if (oAlpha)
9605         {
9606             // Calculate opacity value
9607             // Consider oox/source/vml/vmlformatting.cxx : decodeColor() function.
9608             double fOpacity = static_cast<double>(*oAlpha) * 65535 / ::oox::drawingml::MAX_PERCENT;
9609 
9610             AddToAttrList( m_rExport.SdrExporter().getFlyFillAttrList(), XML_opacity, OString::number(fOpacity) + "f" );
9611         }
9612 
9613         AddToAttrList(m_rExport.SdrExporter().getFlyAttrList(), XML_fillcolor, "#" + sColor );
9614         lclAddThemeFillColorAttributes(m_rExport.SdrExporter().getFlyAttrList(), rComplexColor);
9615     }
9616     else if (m_rExport.SdrExporter().getDMLTextFrameSyntax())
9617     {
9618         bool bImageBackground = false;
9619         const SfxPoolItem* pItem = GetExport().HasItem(XATTR_FILLSTYLE);
9620         if (pItem)
9621         {
9622             const XFillStyleItem* pFillStyle = static_cast<const XFillStyleItem*>(pItem);
9623             if(pFillStyle->GetValue() == drawing::FillStyle_BITMAP)
9624             {
9625                 bImageBackground = true;
9626             }
9627         }
9628         if (!bImageBackground)
9629         {
9630             m_pSerializer->startElementNS(XML_a, XML_solidFill);
9631             m_pSerializer->startElementNS(XML_a, XML_srgbClr, XML_val, sColor);
9632             if (oAlpha)
9633                 m_pSerializer->singleElementNS(XML_a, XML_alpha,
9634                                               XML_val, OString::number(*oAlpha));
9635             m_pSerializer->endElementNS(XML_a, XML_srgbClr);
9636             m_pSerializer->endElementNS(XML_a, XML_solidFill);
9637         }
9638     }
9639     else if ( !m_rExport.m_bOutPageDescs )
9640     {
9641         // compare fill color with the original fill color
9642         OString sOriginalFill = OUStringToOString(
9643                 m_sOriginalBackgroundColor, RTL_TEXTENCODING_UTF8 );
9644 
9645         if ( aColor == COL_AUTO )
9646             sColor = "auto"_ostr;
9647 
9648         if( !m_pBackgroundAttrList.is() )
9649         {
9650             m_pBackgroundAttrList = FastSerializerHelper::createAttrList();
9651             m_pBackgroundAttrList->add(FSNS(XML_w, XML_fill), sColor);
9652             m_pBackgroundAttrList->add( FSNS( XML_w, XML_val ), "clear" );
9653         }
9654         else if ( sOriginalFill != sColor )
9655         {
9656             // fill was modified during edition, theme fill attribute must be dropped
9657             m_pBackgroundAttrList = FastSerializerHelper::createAttrList();
9658             m_pBackgroundAttrList->add(FSNS(XML_w, XML_fill), sColor);
9659             m_pBackgroundAttrList->add( FSNS( XML_w, XML_val ), "clear" );
9660         }
9661         m_sOriginalBackgroundColor.clear();
9662     }
9663 }
9664 
FormatFillStyle(const XFillStyleItem & rFillStyle)9665 void DocxAttributeOutput::FormatFillStyle( const XFillStyleItem& rFillStyle )
9666 {
9667     if (!m_bIgnoreNextFill)
9668         m_oFillStyle = rFillStyle.GetValue();
9669     else
9670     {
9671         m_bIgnoreNextFill = false;
9672         // ITEM: Still need to signal that ::FormatFillStyle was called so that
9673         // ::FormatFillGradient does not assert but do nothing
9674         m_oFillStyle = drawing::FillStyle_NONE;
9675     }
9676 
9677     // Don't round-trip grabbag OriginalBackground if the background has been cleared.
9678     if ( m_pBackgroundAttrList.is() && m_sOriginalBackgroundColor != "auto" && rFillStyle.GetValue() == drawing::FillStyle_NONE )
9679         m_pBackgroundAttrList.clear();
9680 }
9681 
FormatFillGradient(const XFillGradientItem & rFillGradient)9682 void DocxAttributeOutput::FormatFillGradient( const XFillGradientItem& rFillGradient )
9683 {
9684     assert(m_oFillStyle && "ITEM: FormatFillStyle *has* to be called before FormatFillGradient(!)");
9685     if (m_oFillStyle && *m_oFillStyle == drawing::FillStyle_GRADIENT && !m_rExport.SdrExporter().getDMLTextFrameSyntax())
9686     {
9687         const basegfx::BGradient& rGradient = rFillGradient.GetGradientValue();
9688         OString sStartColor = msfilter::util::ConvertColor(Color(rGradient.GetColorStops().front().getStopColor()));
9689         OString sEndColor = msfilter::util::ConvertColor(Color(rGradient.GetColorStops().back().getStopColor()));
9690 
9691         const sal_Int32 nAngle = toDegrees(rGradient.GetAngle());
9692         if (nAngle != 0)
9693             AddToAttrList( m_rExport.SdrExporter().getFlyFillAttrList(),
9694                     XML_angle, OString::number(nAngle));
9695 
9696         // LO does linear gradients top to bottom, while MSO does bottom to top.
9697         // LO does axial gradients inner to outer, while MSO does outer to inner.
9698         // Conclusion: swap start and end colors.
9699         const OString sColor1 = sEndColor; // LO end color is MSO start color
9700         OString sColor2 = sStartColor; // LO start color is MSO end color
9701 
9702         switch (rGradient.GetGradientStyle())
9703         {
9704             case css::awt::GradientStyle_AXIAL:
9705             case css::awt::GradientStyle_LINEAR:
9706             {
9707                 bool bIsSymmetrical = rGradient.GetGradientStyle() == css::awt::GradientStyle_AXIAL;
9708                 if (!bIsSymmetrical)
9709                 {
9710                     const basegfx::BColorStops& rColorStops = rGradient.GetColorStops();
9711                     if (rColorStops.size() > 2 && rColorStops.isSymmetrical())
9712                     {
9713                         for (auto& rStop : rColorStops)
9714                         {
9715                             if (basegfx::fTools::less(rStop.getStopOffset(), 0.5))
9716                                 continue;
9717                             if (basegfx::fTools::more(rStop.getStopOffset(), 0.5))
9718                                 break;
9719 
9720                             // from MSO export perspective, the inner color is the end color
9721                             sColor2 = msfilter::util::ConvertColor(Color(rStop.getStopColor()));
9722                             bIsSymmetrical = true;
9723                         }
9724                     }
9725                 }
9726 
9727                 if (bIsSymmetrical)
9728                     AddToAttrList( m_rExport.SdrExporter().getFlyFillAttrList(), XML_focus, "50%" );
9729 
9730                 AddToAttrList(m_rExport.SdrExporter().getFlyFillAttrList(), XML_type, "gradient");
9731                 break;
9732             }
9733             case css::awt::GradientStyle_RADIAL:
9734             case css::awt::GradientStyle_ELLIPTICAL:
9735             case css::awt::GradientStyle_SQUARE:
9736             case css::awt::GradientStyle_RECT:
9737                 AddToAttrList(m_rExport.SdrExporter().getFlyFillAttrList(), XML_type,
9738                               "gradientRadial");
9739                 break;
9740             default:
9741                 break;
9742         }
9743 
9744         AddToAttrList( m_rExport.SdrExporter().getFlyAttrList(), XML_fillcolor, "#" + sColor1 );
9745         AddToAttrList( m_rExport.SdrExporter().getFlyFillAttrList(), XML_color2, "#" + sColor2 );
9746     }
9747     else if (m_oFillStyle && *m_oFillStyle == drawing::FillStyle_GRADIENT && m_rExport.SdrExporter().getDMLTextFrameSyntax())
9748     {
9749         SwFrameFormat & rFormat(
9750                 const_cast<SwFrameFormat&>(m_rExport.m_pParentFrame->GetFrameFormat()));
9751         rtl::Reference<SwXTextFrame> const xPropertySet =
9752             SwXTextFrame::CreateXTextFrame(*rFormat.GetDoc(), &rFormat);
9753         m_rDrawingML.SetFS(m_pSerializer);
9754         m_rDrawingML.WriteGradientFill(uno::Reference<beans::XPropertySet>(static_cast<SwXFrame*>(xPropertySet.get())));
9755     }
9756     m_oFillStyle.reset();
9757 }
9758 
FormatBox(const SvxBoxItem & rBox)9759 void DocxAttributeOutput::FormatBox( const SvxBoxItem& rBox )
9760 {
9761     if (m_rExport.SdrExporter().getDMLTextFrameSyntax())
9762     {
9763         // ugh, exporting fill here is quite some hack... this OutputItemSet abstraction is quite leaky
9764         // <a:gradFill> should be before <a:ln>.
9765         const SfxPoolItem* pItem = GetExport().HasItem(XATTR_FILLSTYLE);
9766         if (pItem)
9767         {
9768             const XFillStyleItem* pFillStyle = static_cast<const XFillStyleItem*>(pItem);
9769             FormatFillStyle(*pFillStyle);
9770             if (m_oFillStyle && *m_oFillStyle == drawing::FillStyle_BITMAP)
9771             {
9772                 const SdrObject* pSdrObj = m_rExport.m_pParentFrame->GetFrameFormat().FindRealSdrObject();
9773                 if (pSdrObj)
9774                 {
9775                     uno::Reference< drawing::XShape > xShape( const_cast<SdrObject*>(pSdrObj)->getUnoShape(), uno::UNO_QUERY );
9776                     uno::Reference< beans::XPropertySet > xPropertySet( xShape, uno::UNO_QUERY );
9777                     m_rDrawingML.SetFS(m_pSerializer);
9778                     m_rDrawingML.WriteBlipFill(xPropertySet, u"BackGraphic"_ustr);
9779                 }
9780             }
9781         }
9782 
9783         pItem = GetExport().HasItem(XATTR_FILLGRADIENT);
9784         if (pItem)
9785         {
9786             const XFillGradientItem* pFillGradient = static_cast<const XFillGradientItem*>(pItem);
9787             FormatFillGradient(*pFillGradient);
9788         }
9789         m_bIgnoreNextFill = true;
9790     }
9791     if (m_rExport.SdrExporter().getTextFrameSyntax() || m_rExport.SdrExporter().getDMLTextFrameSyntax())
9792     {
9793         const SvxBorderLine* pLeft = rBox.GetLeft( );
9794         const SvxBorderLine* pTop = rBox.GetTop( );
9795         const SvxBorderLine* pRight = rBox.GetRight( );
9796         const SvxBorderLine* pBottom = rBox.GetBottom( );
9797 
9798         if (pLeft && pRight && pTop && pBottom &&
9799                 *pLeft == *pRight && *pLeft == *pTop && *pLeft == *pBottom)
9800         {
9801             // Check border style
9802             SvxBorderLineStyle eBorderStyle = pTop->GetBorderLineStyle();
9803             if (eBorderStyle == SvxBorderLineStyle::NONE)
9804             {
9805                 if (m_rExport.SdrExporter().getTextFrameSyntax())
9806                 {
9807                     AddToAttrList( m_rExport.SdrExporter().getFlyAttrList(),
9808                             XML_stroked, "f", XML_strokeweight, "0pt" );
9809                 }
9810             }
9811             else
9812             {
9813                 OString sColor(msfilter::util::ConvertColor(pTop->GetColor()));
9814                 double const fConverted(editeng::ConvertBorderWidthToWord(pTop->GetBorderLineStyle(), pTop->GetWidth()));
9815 
9816                 if (m_rExport.SdrExporter().getTextFrameSyntax())
9817                 {
9818                     sal_Int32 nWidth = sal_Int32(fConverted / 20);
9819                     AddToAttrList( m_rExport.SdrExporter().getFlyAttrList(),
9820                             XML_strokecolor, "#" + sColor,
9821                             XML_strokeweight, OString::number(nWidth) + "pt" );
9822                     if( SvxBorderLineStyle::DASHED == pTop->GetBorderLineStyle() ) // Line Style is Dash type
9823                         AddToAttrList( m_rExport.SdrExporter().getDashLineStyle(),
9824                             XML_dashstyle, "dash" );
9825                 }
9826                 else
9827                     m_rExport.SdrExporter().writeBoxItemLine(rBox);
9828             }
9829         }
9830 
9831         if (m_rExport.SdrExporter().getDMLTextFrameSyntax())
9832         {
9833             m_rExport.SdrExporter().getBodyPrAttrList()->add(XML_lIns, OString::number(TwipsToEMU(rBox.GetDistance(SvxBoxItemLine::LEFT))));
9834             m_rExport.SdrExporter().getBodyPrAttrList()->add(XML_tIns, OString::number(TwipsToEMU(rBox.GetDistance(SvxBoxItemLine::TOP))));
9835             m_rExport.SdrExporter().getBodyPrAttrList()->add(XML_rIns, OString::number(TwipsToEMU(rBox.GetDistance(SvxBoxItemLine::RIGHT))));
9836             m_rExport.SdrExporter().getBodyPrAttrList()->add(XML_bIns, OString::number(TwipsToEMU(rBox.GetDistance(SvxBoxItemLine::BOTTOM))));
9837             return;
9838         }
9839 
9840         // v:textbox's inset attribute: inner margin values for textbox text - write only non-default values
9841         double fDistanceLeftTwips = double(rBox.GetDistance(SvxBoxItemLine::LEFT));
9842         double fDistanceTopTwips = double(rBox.GetDistance(SvxBoxItemLine::TOP));
9843         double fDistanceRightTwips = double(rBox.GetDistance(SvxBoxItemLine::RIGHT));
9844         double fDistanceBottomTwips = double(rBox.GetDistance(SvxBoxItemLine::BOTTOM));
9845 
9846         // Convert 'TWIPS' to 'INCH' (because in Word the default values are in Inches)
9847         double fDistanceLeftInch = o3tl::convert(fDistanceLeftTwips, o3tl::Length::twip, o3tl::Length::in);
9848         double fDistanceTopInch = o3tl::convert(fDistanceTopTwips, o3tl::Length::twip, o3tl::Length::in);
9849         double fDistanceRightInch = o3tl::convert(fDistanceRightTwips, o3tl::Length::twip, o3tl::Length::in);
9850         double fDistanceBottomInch = o3tl::convert(fDistanceBottomTwips, o3tl::Length::twip, o3tl::Length::in);
9851 
9852         // This code will write ONLY the non-default values. The values are in 'left','top','right','bottom' order.
9853         // so 'bottom' is checked if it is default and if it is non-default - all the values will be written
9854         // otherwise - 'right' is checked if it is default and if it is non-default - all the values except for 'bottom' will be written
9855         // and so on.
9856         OStringBuffer aInset;
9857         if(!aInset.isEmpty() || fDistanceBottomInch != 0.05)
9858             aInset.insert(0, Concat2View("," + OString::number(fDistanceBottomInch) + "in"));
9859 
9860         if(!aInset.isEmpty() || fDistanceRightInch != 0.1)
9861             aInset.insert(0, Concat2View("," + OString::number(fDistanceRightInch) + "in"));
9862 
9863         if(!aInset.isEmpty() || fDistanceTopInch != 0.05)
9864             aInset.insert(0, Concat2View("," + OString::number(fDistanceTopInch) + "in"));
9865 
9866         if(!aInset.isEmpty() || fDistanceLeftInch != 0.1)
9867             aInset.insert(0, Concat2View(OString::number(fDistanceLeftInch) + "in"));
9868 
9869         if (!aInset.isEmpty())
9870             m_rExport.SdrExporter().getTextboxAttrList()->add(XML_inset, aInset);
9871 
9872         return;
9873     }
9874 
9875     OutputBorderOptions aOutputBorderOptions = lcl_getBoxBorderOptions();
9876     // Check if there is a shadow item
9877     const SfxPoolItem* pItem = GetExport().HasItem( RES_SHADOW );
9878     if ( pItem )
9879     {
9880         const SvxShadowItem* pShadowItem = static_cast<const SvxShadowItem*>(pItem);
9881         aOutputBorderOptions.aShadowLocation = pShadowItem->GetLocation();
9882     }
9883 
9884     if ( m_bOpenedSectPr && !GetWritingHeaderFooter())
9885         return;
9886 
9887     // Not inside a section
9888 
9889     // Open the paragraph's borders tag
9890     m_pSerializer->startElementNS(XML_w, XML_pBdr);
9891 
9892     std::map<SvxBoxItemLine, css::table::BorderLine2> aStyleBorders;
9893     const SvxBoxItem* pInherited = nullptr;
9894     if ( GetExport().m_pStyAttr )
9895         pInherited = GetExport().m_pStyAttr->GetItem<SvxBoxItem>(RES_BOX);
9896     else if ( GetExport().m_pCurrentStyle && GetExport().m_pCurrentStyle->DerivedFrom() )
9897         pInherited = GetExport().m_pCurrentStyle->DerivedFrom()->GetAttrSet().GetItem<SvxBoxItem>(RES_BOX);
9898 
9899     if ( pInherited )
9900     {
9901         aStyleBorders[ SvxBoxItemLine::TOP ] = SvxBoxItem::SvxLineToLine(pInherited->GetTop(), /*bConvert=*/false);
9902         aStyleBorders[ SvxBoxItemLine::BOTTOM ] = SvxBoxItem::SvxLineToLine(pInherited->GetBottom(), false);
9903         aStyleBorders[ SvxBoxItemLine::LEFT ] = SvxBoxItem::SvxLineToLine(pInherited->GetLeft(), false);
9904         aStyleBorders[ SvxBoxItemLine::RIGHT ] = SvxBoxItem::SvxLineToLine(pInherited->GetRight(), false);
9905     }
9906     bool bUseFrame = m_aFramePr.UseFrameBorders(!m_xTableWrt ? -1 : m_tableReference.m_nTableDepth);
9907     impl_borders(m_pSerializer, rBox, aOutputBorderOptions, aStyleBorders,
9908                  bUseFrame ? m_aFramePr.Frame() : nullptr);
9909 
9910     // Close the paragraph's borders tag
9911     m_pSerializer->endElementNS( XML_w, XML_pBdr );
9912 
9913     m_aFramePr.SetUseFrameBorders(false);
9914 }
9915 
FormatColumns_Impl(sal_uInt16 nCols,const SwFormatCol & rCol,bool bEven,SwTwips nPageSize)9916 void DocxAttributeOutput::FormatColumns_Impl( sal_uInt16 nCols, const SwFormatCol& rCol, bool bEven, SwTwips nPageSize )
9917 {
9918     // Get the columns attributes
9919     rtl::Reference<FastAttributeList> pColsAttrList = FastSerializerHelper::createAttrList();
9920 
9921     pColsAttrList->add( FSNS( XML_w, XML_num ), OString::number( nCols ) );
9922 
9923     std::string_view pEquals = "false";
9924     if ( bEven )
9925     {
9926         sal_uInt16 nWidth = rCol.GetGutterWidth( true );
9927         pColsAttrList->add( FSNS( XML_w, XML_space ), OString::number( nWidth ) );
9928 
9929         pEquals = "true";
9930     }
9931 
9932     pColsAttrList->add( FSNS( XML_w, XML_equalWidth ), pEquals );
9933 
9934     bool bHasSep = (COLADJ_NONE != rCol.GetLineAdj());
9935 
9936     pColsAttrList->add( FSNS( XML_w, XML_sep ), OString::boolean( bHasSep ) );
9937 
9938     // Write the element
9939     m_pSerializer->startElementNS( XML_w, XML_cols, pColsAttrList );
9940 
9941     // Write the columns width if non-equals
9942     const SwColumns & rColumns = rCol.GetColumns(  );
9943     if ( !bEven )
9944     {
9945         for ( sal_uInt16 n = 0; n < nCols; ++n )
9946         {
9947             rtl::Reference<FastAttributeList> pColAttrList = FastSerializerHelper::createAttrList();
9948             sal_uInt16 nWidth = rCol.CalcPrtColWidth( n, o3tl::narrowing<sal_uInt16>(nPageSize) );
9949             pColAttrList->add( FSNS( XML_w, XML_w ), OString::number( nWidth ) );
9950 
9951             if ( n + 1 != nCols )
9952             {
9953                 sal_uInt16 nSpacing = rColumns[n].GetRight( ) + rColumns[n + 1].GetLeft( );
9954                 pColAttrList->add( FSNS( XML_w, XML_space ), OString::number( nSpacing ) );
9955             }
9956 
9957             m_pSerializer->singleElementNS( XML_w, XML_col, pColAttrList );
9958         }
9959     }
9960 
9961     m_pSerializer->endElementNS( XML_w, XML_cols );
9962 }
9963 
FormatKeep(const SvxFormatKeepItem & rItem)9964 void DocxAttributeOutput::FormatKeep( const SvxFormatKeepItem& rItem )
9965 {
9966     m_pSerializer->singleElementNS( XML_w, XML_keepNext,
9967             FSNS( XML_w, XML_val ), OString::boolean( rItem.GetValue() ) );
9968 }
9969 
FormatTextGrid(const SwTextGridItem & rGrid)9970 void DocxAttributeOutput::FormatTextGrid( const SwTextGridItem& rGrid )
9971 {
9972     rtl::Reference<FastAttributeList> pGridAttrList = FastSerializerHelper::createAttrList();
9973 
9974     std::string_view sGridType;
9975     switch ( rGrid.GetGridType( ) )
9976     {
9977         default:
9978         case GRID_NONE:
9979             sGridType = "default";
9980             break;
9981         case GRID_LINES_ONLY:
9982             sGridType = "lines";
9983             break;
9984         case GRID_LINES_CHARS:
9985             if ( rGrid.IsSnapToChars( ) )
9986                 sGridType = "snapToChars";
9987             else
9988                 sGridType = "linesAndChars";
9989             break;
9990     }
9991     pGridAttrList->add(FSNS(XML_w, XML_type), sGridType);
9992 
9993     sal_uInt16 nHeight = rGrid.GetBaseHeight() + rGrid.GetRubyHeight();
9994     pGridAttrList->add( FSNS( XML_w, XML_linePitch ),
9995             OString::number( nHeight ) );
9996 
9997     pGridAttrList->add( FSNS( XML_w, XML_charSpace ),
9998             OString::number( GridCharacterPitch( rGrid ) ) );
9999 
10000     m_pSerializer->singleElementNS( XML_w, XML_docGrid, pGridAttrList );
10001 }
10002 
FormatLineNumbering(const SwFormatLineNumber & rNumbering)10003 void DocxAttributeOutput::FormatLineNumbering( const SwFormatLineNumber& rNumbering )
10004 {
10005     if ( !rNumbering.IsCount( ) )
10006         m_pSerializer->singleElementNS(XML_w, XML_suppressLineNumbers);
10007     else
10008         m_pSerializer->singleElementNS(XML_w, XML_suppressLineNumbers, FSNS(XML_w, XML_val), "0");
10009 }
10010 
FormatFrameDirection(const SvxFrameDirectionItem & rDirection)10011 void DocxAttributeOutput::FormatFrameDirection( const SvxFrameDirectionItem& rDirection )
10012 {
10013     OString sTextFlow;
10014     bool bBiDi = false;
10015     SvxFrameDirection nDir = rDirection.GetValue();
10016 
10017     if ( nDir == SvxFrameDirection::Environment )
10018         nDir = GetExport( ).GetDefaultFrameDirection( );
10019 
10020     switch ( nDir )
10021     {
10022         default:
10023         case SvxFrameDirection::Horizontal_LR_TB:
10024             sTextFlow = "lrTb"_ostr;
10025             break;
10026         case SvxFrameDirection::Horizontal_RL_TB:
10027             sTextFlow = "lrTb"_ostr;
10028             bBiDi = true;
10029             break;
10030         case SvxFrameDirection::Vertical_LR_TB: // ~ vert="mongolianVert"
10031             sTextFlow = "tbLrV"_ostr;
10032             break;
10033         case SvxFrameDirection::Vertical_RL_TB: // ~ vert="eaVert"
10034             sTextFlow = "tbRl"_ostr;
10035             break;
10036         case SvxFrameDirection::Vertical_LR_BT: // ~ vert="vert270"
10037             sTextFlow = "btLr"_ostr;
10038             break;
10039         case SvxFrameDirection::Vertical_RL_TB90: // ~ vert="vert"
10040             sTextFlow = "tbRlV"_ostr;
10041             break;
10042     }
10043 
10044     if ( m_rExport.m_bOutPageDescs )
10045     {
10046         m_pSerializer->singleElementNS(XML_w, XML_textDirection, FSNS(XML_w, XML_val), sTextFlow);
10047         if ( bBiDi )
10048             m_pSerializer->singleElementNS(XML_w, XML_bidi);
10049     }
10050     else if ( !m_rExport.m_bOutFlyFrameAttrs )
10051     {
10052         if ( bBiDi )
10053             m_pSerializer->singleElementNS(XML_w, XML_bidi, FSNS(XML_w, XML_val), "1");
10054         else
10055             m_pSerializer->singleElementNS(XML_w, XML_bidi, FSNS(XML_w, XML_val), "0");
10056         m_aFramePr.SetUseFrameTextDirection(false);
10057     }
10058 }
10059 
ParaGrabBag(const SfxGrabBagItem & rItem)10060 void DocxAttributeOutput::ParaGrabBag(const SfxGrabBagItem& rItem)
10061 {
10062     const std::map<OUString, css::uno::Any>& rMap = rItem.GetGrabBag();
10063     for ( const auto & rGrabBagElement : rMap )
10064     {
10065         if (rGrabBagElement.first == "MirrorIndents")
10066             m_pSerializer->singleElementNS(XML_w, XML_mirrorIndents);
10067         else if (rGrabBagElement.first == "ParaTopMarginBeforeAutoSpacing")
10068         {
10069             m_bParaBeforeAutoSpacing = true;
10070             // get fixed value which was set during import
10071             rGrabBagElement.second >>= m_nParaBeforeSpacing;
10072             m_nParaBeforeSpacing = o3tl::toTwips(m_nParaBeforeSpacing, o3tl::Length::mm100);
10073             SAL_INFO("sw.ww8", "DocxAttributeOutput::ParaGrabBag: property =" << rGrabBagElement.first << " : m_nParaBeforeSpacing= " << m_nParaBeforeSpacing);
10074         }
10075         else if (rGrabBagElement.first == "ParaBottomMarginAfterAutoSpacing")
10076         {
10077             m_bParaAfterAutoSpacing = true;
10078             // get fixed value which was set during import
10079             rGrabBagElement.second >>= m_nParaAfterSpacing;
10080             m_nParaAfterSpacing = o3tl::toTwips(m_nParaAfterSpacing, o3tl::Length::mm100);
10081             SAL_INFO("sw.ww8", "DocxAttributeOutput::ParaGrabBag: property =" << rGrabBagElement.first << " : m_nParaBeforeSpacing= " << m_nParaAfterSpacing);
10082         }
10083         else if (rGrabBagElement.first == "CharThemeFill")
10084         {
10085             uno::Sequence<beans::PropertyValue> aGrabBagSeq;
10086             rGrabBagElement.second >>= aGrabBagSeq;
10087 
10088             for (const auto& rProp : aGrabBagSeq)
10089             {
10090                 OUString sVal = rProp.Value.get<OUString>();
10091 
10092                 if (sVal.isEmpty())
10093                     continue;
10094 
10095                 if (rProp.Name == "val")
10096                     AddToAttrList(m_pBackgroundAttrList, FSNS(XML_w, XML_val), sVal);
10097                 else if (rProp.Name == "color")
10098                     AddToAttrList(m_pBackgroundAttrList, FSNS(XML_w, XML_color), sVal);
10099                 else if (rProp.Name == "themeColor")
10100                     AddToAttrList(m_pBackgroundAttrList, FSNS(XML_w, XML_themeColor), sVal);
10101                 else if (rProp.Name == "themeTint")
10102                     AddToAttrList(m_pBackgroundAttrList, FSNS(XML_w, XML_themeTint), sVal);
10103                 else if (rProp.Name == "themeShade")
10104                     AddToAttrList(m_pBackgroundAttrList, FSNS(XML_w, XML_themeShade), sVal);
10105                 else if (rProp.Name == "fill")
10106                     AddToAttrList(m_pBackgroundAttrList, FSNS(XML_w, XML_fill), sVal);
10107                 else if (rProp.Name == "themeFill")
10108                     AddToAttrList(m_pBackgroundAttrList, FSNS(XML_w, XML_themeFill), sVal);
10109                 else if (rProp.Name == "themeFillTint")
10110                     AddToAttrList(m_pBackgroundAttrList, FSNS(XML_w, XML_themeFillTint), sVal);
10111                 else if (rProp.Name == "themeFillShade")
10112                     AddToAttrList(m_pBackgroundAttrList, FSNS(XML_w, XML_themeFillShade), sVal);
10113                 else if (rProp.Name == "originalColor")
10114                     rProp.Value >>= m_sOriginalBackgroundColor;
10115             }
10116         }
10117         else if (rGrabBagElement.first == "SdtPr")
10118         {
10119             const uno::Sequence<beans::PropertyValue> aGrabBagSdt =
10120                     rGrabBagElement.second.get< uno::Sequence<beans::PropertyValue> >();
10121             m_aParagraphSdt.GetSdtParamsFromGrabBag(aGrabBagSdt);
10122             m_aStartedParagraphSdtPrAlias = m_aParagraphSdt.m_aAlias;
10123         }
10124         else if (rGrabBagElement.first == "ParaCnfStyle")
10125         {
10126             uno::Sequence<beans::PropertyValue> aAttributes = rGrabBagElement.second.get< uno::Sequence<beans::PropertyValue> >();
10127             m_pTableStyleExport->CnfStyle(aAttributes);
10128         }
10129         else if (rGrabBagElement.first == "ParaSdtEndBefore")
10130         {
10131             // Handled already in StartParagraph().
10132         }
10133         else
10134             SAL_WARN("sw.ww8", "DocxAttributeOutput::ParaGrabBag: unhandled grab bag property " << rGrabBagElement.first );
10135     }
10136 }
10137 
CharGrabBag(const SfxGrabBagItem & rItem)10138 void DocxAttributeOutput::CharGrabBag( const SfxGrabBagItem& rItem )
10139 {
10140     if (m_bPreventDoubleFieldsHandling)
10141         return;
10142 
10143     const std::map< OUString, css::uno::Any >& rMap = rItem.GetGrabBag();
10144 
10145     // get original values of theme-derived properties to check if they have changed during the edition
10146     bool bWriteCSTheme = true;
10147     bool bWriteAsciiTheme = true;
10148     bool bWriteEastAsiaTheme = true;
10149     OUString sOriginalValue;
10150     for ( const auto & rGrabBagElement : rMap )
10151     {
10152         if ( m_pFontsAttrList.is() && rGrabBagElement.first == "CharThemeFontNameCs" )
10153         {
10154             if ( rGrabBagElement.second >>= sOriginalValue )
10155                 bWriteCSTheme =
10156                         ( m_pFontsAttrList->getOptionalValue( FSNS( XML_w, XML_cs ) ) == sOriginalValue );
10157         }
10158         else if ( m_pFontsAttrList.is() && rGrabBagElement.first == "CharThemeFontNameAscii" )
10159         {
10160             if ( rGrabBagElement.second >>= sOriginalValue )
10161                 bWriteAsciiTheme =
10162                         ( m_pFontsAttrList->getOptionalValue( FSNS( XML_w, XML_ascii ) ) == sOriginalValue );
10163         }
10164         else if ( m_pFontsAttrList.is() && rGrabBagElement.first == "CharThemeFontNameEastAsia" )
10165         {
10166             if ( rGrabBagElement.second >>= sOriginalValue )
10167                 bWriteEastAsiaTheme =
10168                         ( m_pFontsAttrList->getOptionalValue( FSNS( XML_w, XML_eastAsia ) ) == sOriginalValue );
10169         }
10170     }
10171 
10172     // save theme attributes back to the run properties
10173     OUString str;
10174     for ( const auto & rGrabBagElement : rMap )
10175     {
10176         if ( rGrabBagElement.first == "CharThemeNameAscii" && bWriteAsciiTheme )
10177         {
10178             rGrabBagElement.second >>= str;
10179             AddToAttrList( m_pFontsAttrList, FSNS( XML_w, XML_asciiTheme ), str );
10180         }
10181         else if ( rGrabBagElement.first == "CharThemeNameCs" && bWriteCSTheme )
10182         {
10183             rGrabBagElement.second >>= str;
10184             AddToAttrList( m_pFontsAttrList, FSNS( XML_w, XML_cstheme ), str );
10185         }
10186         else if ( rGrabBagElement.first == "CharThemeNameEastAsia" && bWriteEastAsiaTheme )
10187         {
10188             rGrabBagElement.second >>= str;
10189             AddToAttrList( m_pFontsAttrList, FSNS( XML_w, XML_eastAsiaTheme ), str );
10190         }
10191         else if ( rGrabBagElement.first == "CharThemeNameHAnsi" && bWriteAsciiTheme )
10192         // this is not a mistake: in LibO we don't directly support the hAnsi family
10193         // of attributes so we save the same value from ascii attributes instead
10194         {
10195             rGrabBagElement.second >>= str;
10196             AddToAttrList( m_pFontsAttrList, FSNS( XML_w, XML_hAnsiTheme ), str );
10197         }
10198         else if( rGrabBagElement.first == "CharThemeFontNameCs"   ||
10199                 rGrabBagElement.first == "CharThemeFontNameAscii" ||
10200                 rGrabBagElement.first == "CharThemeFontNameEastAsia" ||
10201                 rGrabBagElement.first == "CharThemeOriginalColor" )
10202         {
10203             // just skip these, they were processed before
10204         }
10205         else if(rGrabBagElement.first == "CharGlowTextEffect" ||
10206                 rGrabBagElement.first == "CharShadowTextEffect" ||
10207                 rGrabBagElement.first == "CharReflectionTextEffect" ||
10208                 rGrabBagElement.first == "CharTextOutlineTextEffect" ||
10209                 rGrabBagElement.first == "CharTextFillTextEffect" ||
10210                 rGrabBagElement.first == "CharScene3DTextEffect" ||
10211                 rGrabBagElement.first == "CharProps3DTextEffect" ||
10212                 rGrabBagElement.first == "CharLigaturesTextEffect" ||
10213                 rGrabBagElement.first == "CharNumFormTextEffect" ||
10214                 rGrabBagElement.first == "CharNumSpacingTextEffect" ||
10215                 rGrabBagElement.first == "CharStylisticSetsTextEffect" ||
10216                 rGrabBagElement.first == "CharCntxtAltsTextEffect")
10217         {
10218             beans::PropertyValue aPropertyValue;
10219             rGrabBagElement.second >>= aPropertyValue;
10220             m_aTextEffectsGrabBag.push_back(aPropertyValue);
10221         }
10222         else if (rGrabBagElement.first == "SdtEndBefore")
10223         {
10224             if (m_aRunSdt.m_bStartedSdt)
10225                 m_bEndCharSdt = true;
10226         }
10227         else if (rGrabBagElement.first == "SdtPr" && FLY_NOT_PROCESSED != m_nStateOfFlyFrame )
10228         {
10229             const uno::Sequence<beans::PropertyValue> aGrabBagSdt =
10230                     rGrabBagElement.second.get< uno::Sequence<beans::PropertyValue> >();
10231             m_aRunSdt.GetSdtParamsFromGrabBag(aGrabBagSdt);
10232         }
10233         else
10234             SAL_INFO("sw.ww8", "DocxAttributeOutput::CharGrabBag: unhandled grab bag property " << rGrabBagElement.first);
10235     }
10236 }
10237 
DocxAttributeOutput(DocxExport & rExport,const FSHelperPtr & pSerializer,oox::drawingml::DrawingML * pDrawingML)10238 DocxAttributeOutput::DocxAttributeOutput( DocxExport &rExport, const FSHelperPtr& pSerializer, oox::drawingml::DrawingML* pDrawingML )
10239     : AttributeOutputBase(rExport.GetFilter().getFileUrl()),
10240       m_rExport( rExport ),
10241       m_pSerializer( pSerializer ),
10242       m_rDrawingML( *pDrawingML ),
10243       m_bEndCharSdt(false),
10244       m_endPageRef( false ),
10245       m_pFootnotesList( new ::docx::FootnotesList() ),
10246       m_pEndnotesList( new ::docx::FootnotesList() ),
10247       m_footnoteEndnoteRefTag( 0 ),
10248       m_pRedlineData( nullptr ),
10249       m_nRedlineId( 0 ),
10250       m_bOpenedSectPr( false ),
10251       m_bHadSectPr(false),
10252       m_bRunTextIsOn( false ),
10253       m_bWritingHeaderFooter( false ),
10254       m_bAnchorLinkedToNode(false),
10255       m_bWritingField( false ),
10256       m_bPreventDoubleFieldsHandling( false ),
10257       m_nNextBookmarkId( 0 ),
10258       m_nNextAnnotationMarkId( 0 ),
10259       m_nEmbedFlyLevel(0),
10260       m_pMoveRedlineData(nullptr),
10261       m_bParagraphOpened( false ),
10262       m_bParagraphFrameOpen( false ),
10263       m_bIsFirstParagraph( true ),
10264       m_bAlternateContentChoiceOpen( false ),
10265       m_bPostponedProcessingFly( false ),
10266       m_nColBreakStatus( COLBRK_NONE ),
10267       m_bPostponedPageBreak( false ),
10268       m_nTextFrameLevel( 0 ),
10269       m_closeHyperlinkInThisRun( false ),
10270       m_closeHyperlinkInPreviousRun( false ),
10271       m_nFieldsInHyperlink( 0 ),
10272       m_bExportingOutline(false),
10273       m_nChartCount(0),
10274       m_PendingPlaceholder( nullptr ),
10275       m_postitFieldsMaxId( 0 ),
10276       m_anchorId( 1 ),
10277       m_nextFontId( 1 ),
10278       m_bIgnoreNextFill(false),
10279       m_pTableStyleExport(std::make_shared<DocxTableStyleExport>(rExport.m_rDoc, pSerializer)),
10280       m_bParaBeforeAutoSpacing(false),
10281       m_bParaAfterAutoSpacing(false),
10282       m_nParaBeforeSpacing(0),
10283       m_nParaAfterSpacing(0)
10284     , m_nStateOfFlyFrame( FLY_NOT_PROCESSED )
10285 {
10286     m_nHyperLinkCount.push_back(0);
10287 }
10288 
~DocxAttributeOutput()10289 DocxAttributeOutput::~DocxAttributeOutput()
10290 {
10291 }
10292 
GetExport()10293 DocxExport& DocxAttributeOutput::GetExport()
10294 {
10295     return m_rExport;
10296 }
10297 
SetSerializer(::sax_fastparser::FSHelperPtr const & pSerializer)10298 void DocxAttributeOutput::SetSerializer( ::sax_fastparser::FSHelperPtr const & pSerializer )
10299 {
10300     m_pSerializer = pSerializer;
10301     m_pTableStyleExport->SetSerializer(pSerializer);
10302 }
10303 
HasFootnotes() const10304 bool DocxAttributeOutput::HasFootnotes() const
10305 {
10306     return !m_pFootnotesList->isEmpty();
10307 }
10308 
HasEndnotes() const10309 bool DocxAttributeOutput::HasEndnotes() const
10310 {
10311     return !m_pEndnotesList->isEmpty();
10312 }
10313 
HasPostitFields() const10314 bool DocxAttributeOutput::HasPostitFields() const
10315 {
10316     return !m_postitFields.empty();
10317 }
10318 
BulletDefinition(int nId,const Graphic & rGraphic,Size aSize)10319 void DocxAttributeOutput::BulletDefinition(int nId, const Graphic& rGraphic, Size aSize)
10320 {
10321     m_pSerializer->startElementNS(XML_w, XML_numPicBullet,
10322             FSNS(XML_w, XML_numPicBulletId), OString::number(nId));
10323 
10324     // Size is in twips, we need it in points.
10325     OString aStyle = "width:" + OString::number(double(aSize.Width()) / 20)+ "pt;"
10326                      "height:" + OString::number(double(aSize.Height()) / 20) + "pt";
10327     m_pSerializer->startElementNS(XML_w, XML_pict);
10328     m_pSerializer->startElementNS( XML_v, XML_shape,
10329             XML_style, aStyle,
10330             FSNS(XML_o, XML_bullet), "t");
10331 
10332     OUString aRelId = m_rDrawingML.writeGraphicToStorage(rGraphic);
10333     m_pSerializer->singleElementNS( XML_v, XML_imagedata,
10334             FSNS(XML_r, XML_id), aRelId,
10335             FSNS(XML_o, XML_title), "");
10336 
10337     m_pSerializer->endElementNS(XML_v, XML_shape);
10338     m_pSerializer->endElementNS(XML_w, XML_pict);
10339 
10340     m_pSerializer->endElementNS(XML_w, XML_numPicBullet);
10341 }
10342 
10343 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
10344