tdf#147067 Jump to clicked spot if left mouse click with Option key
[LibreOffice.git] / sw / source / filter / ww8 / docxattributeoutput.cxx
blob69408eddad6182b0dbd45e52c3c8ed3d33783a29
1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
3 * This file is part of the LibreOffice project.
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/.
9 * This file incorporates work covered by the following license notice:
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 .
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"
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>
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>
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>
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>
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>
155 #include <algorithm>
156 #include <cstddef>
157 #include <stdarg.h>
158 #include <string_view>
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 #include <unotxdoc.hxx>
168 #include <poolfmt.hxx>
170 using ::editeng::SvxBorderLine;
172 using namespace oox;
173 using namespace docx;
174 using namespace sax_fastparser;
175 using namespace nsSwDocInfoSubType;
176 using namespace sw::util;
177 using namespace ::com::sun::star;
178 using namespace ::com::sun::star::drawing;
180 namespace {
182 class FFDataWriterHelper
184 ::sax_fastparser::FSHelperPtr m_pSerializer;
185 void writeCommonStart( const OUString& rName,
186 const OUString& rEntryMacro,
187 const OUString& rExitMacro,
188 const OUString& rHelp,
189 const OUString& rHint )
191 m_pSerializer->startElementNS(XML_w, XML_ffData);
192 m_pSerializer->singleElementNS(XML_w, XML_name, FSNS(XML_w, XML_val), rName);
193 m_pSerializer->singleElementNS(XML_w, XML_enabled);
194 m_pSerializer->singleElementNS(XML_w, XML_calcOnExit, FSNS(XML_w, XML_val), "0");
196 if ( !rEntryMacro.isEmpty() )
197 m_pSerializer->singleElementNS( XML_w, XML_entryMacro,
198 FSNS(XML_w, XML_val), rEntryMacro );
200 if ( !rExitMacro.isEmpty() )
201 m_pSerializer->singleElementNS(XML_w, XML_exitMacro, FSNS(XML_w, XML_val), rExitMacro);
203 if ( !rHelp.isEmpty() )
204 m_pSerializer->singleElementNS( XML_w, XML_helpText,
205 FSNS(XML_w, XML_type), "text",
206 FSNS(XML_w, XML_val), rHelp );
208 if ( !rHint.isEmpty() )
209 m_pSerializer->singleElementNS( XML_w, XML_statusText,
210 FSNS(XML_w, XML_type), "text",
211 FSNS(XML_w, XML_val), rHint );
214 void writeFinish()
216 m_pSerializer->endElementNS( XML_w, XML_ffData );
218 public:
219 explicit FFDataWriterHelper( ::sax_fastparser::FSHelperPtr rSerializer ) : m_pSerializer(std::move( rSerializer )){}
220 void WriteFormCheckbox( const OUString& rName,
221 const OUString& rEntryMacro,
222 const OUString& rExitMacro,
223 const OUString& rHelp,
224 const OUString& rHint,
225 bool bChecked )
227 writeCommonStart( rName, rEntryMacro, rExitMacro, rHelp, rHint );
228 // Checkbox specific bits
229 m_pSerializer->startElementNS(XML_w, XML_checkBox);
230 // currently hardcoding autosize
231 // #TODO check if this defaulted
232 m_pSerializer->startElementNS(XML_w, XML_sizeAuto);
233 m_pSerializer->endElementNS( XML_w, XML_sizeAuto );
234 if ( bChecked )
235 m_pSerializer->singleElementNS(XML_w, XML_checked);
236 m_pSerializer->endElementNS( XML_w, XML_checkBox );
237 writeFinish();
240 void WriteFormText( const OUString& rName,
241 const OUString& rEntryMacro,
242 const OUString& rExitMacro,
243 const OUString& rHelp,
244 const OUString& rHint,
245 const OUString& rType,
246 const OUString& rDefaultText,
247 sal_uInt16 nMaxLength,
248 const OUString& rFormat )
250 writeCommonStart( rName, rEntryMacro, rExitMacro, rHelp, rHint );
252 m_pSerializer->startElementNS(XML_w, XML_textInput);
253 if ( !rType.isEmpty() )
254 m_pSerializer->singleElementNS(XML_w, XML_type, FSNS(XML_w, XML_val), rType);
255 if ( !rDefaultText.isEmpty() )
256 m_pSerializer->singleElementNS(XML_w, XML_default, FSNS(XML_w, XML_val), rDefaultText);
257 if ( nMaxLength )
258 m_pSerializer->singleElementNS( XML_w, XML_maxLength,
259 FSNS(XML_w, XML_val), OString::number(nMaxLength) );
260 if ( !rFormat.isEmpty() )
261 m_pSerializer->singleElementNS(XML_w, XML_format, FSNS(XML_w, XML_val), rFormat);
262 m_pSerializer->endElementNS( XML_w, XML_textInput );
264 writeFinish();
268 class FieldMarkParamsHelper
270 const sw::mark::Fieldmark& mrFieldmark;
271 public:
272 explicit FieldMarkParamsHelper( const sw::mark::Fieldmark& rFieldmark ) : mrFieldmark( rFieldmark ) {}
273 OUString const & getName() const { return mrFieldmark.GetName(); }
274 template < typename T >
275 bool extractParam( const OUString& rKey, T& rResult )
277 bool bResult = false;
278 if ( mrFieldmark.GetParameters() )
280 sw::mark::Fieldmark::parameter_map_t::const_iterator it = mrFieldmark.GetParameters()->find( rKey );
281 if ( it != mrFieldmark.GetParameters()->end() )
282 bResult = ( it->second >>= rResult );
284 return bResult;
288 // [ISO/IEC29500-1:2016] 17.18.50 ST_LongHexNumber (Eight Digit Hexadecimal Value)
289 OUString NumberToHexBinary(sal_Int32 n)
291 OUStringBuffer aBuf;
292 sax::Converter::convertNumberToHexBinary(aBuf, n);
293 return aBuf.makeStringAndClear();
296 // Returns a new reference with the previous content of src; src is empty after this
297 auto detachFrom(rtl::Reference<sax_fastparser::FastAttributeList>& src)
299 return rtl::Reference(std::move(src));
302 constexpr auto constThemeColorTypeTokenMap = frozen::make_unordered_map<model::ThemeColorType, const char*>({
303 { model::ThemeColorType::Dark1, "dark1" },
304 { model::ThemeColorType::Light1, "light1" },
305 { model::ThemeColorType::Dark2, "dark2" },
306 { model::ThemeColorType::Light2, "light2" },
307 { model::ThemeColorType::Accent1, "accent1" },
308 { model::ThemeColorType::Accent2, "accent2" },
309 { model::ThemeColorType::Accent3, "accent3" },
310 { model::ThemeColorType::Accent4, "accent4" },
311 { model::ThemeColorType::Accent5, "accent5" },
312 { model::ThemeColorType::Accent6, "accent6" },
313 { model::ThemeColorType::Hyperlink, "hyperlink" },
314 { model::ThemeColorType::FollowedHyperlink, "followedHyperlink" }
317 OString lclGetSchemeType(model::ComplexColor const& rComplexColor)
319 const auto iter = constThemeColorTypeTokenMap.find(rComplexColor.getThemeColorType());
320 assert(iter != constThemeColorTypeTokenMap.end());
321 OString sSchemeType = iter->second;
322 if (rComplexColor.getThemeColorUsage() == model::ThemeColorUsage::Text)
324 if (rComplexColor.getThemeColorType() == model::ThemeColorType::Dark1)
325 sSchemeType = "text1"_ostr;
326 else if (rComplexColor.getThemeColorType() == model::ThemeColorType::Dark2)
327 sSchemeType = "text2"_ostr;
329 else if (rComplexColor.getThemeColorUsage() == model::ThemeColorUsage::Background)
331 if (rComplexColor.getThemeColorType() == model::ThemeColorType::Light1)
332 sSchemeType = "background1"_ostr;
333 else if (rComplexColor.getThemeColorType() == model::ThemeColorType::Light2)
334 sSchemeType = "background2"_ostr;
336 return sSchemeType;
339 void lclAddThemeValuesToCustomAttributes(
340 rtl::Reference<sax_fastparser::FastAttributeList>& pAttrList, model::ComplexColor const& rComplexColor,
341 sal_Int32 nThemeAttrId, sal_Int32 nThemeTintAttrId, sal_Int32 nThemeShadeAttrId)
343 if (rComplexColor.isValidThemeType())
345 OString sSchemeType = lclGetSchemeType(rComplexColor);
347 DocxAttributeOutput::AddToAttrList(pAttrList, FSNS(XML_w, nThemeAttrId), sSchemeType);
349 sal_Int16 nLumMod = 10'000;
350 sal_Int16 nLumOff = 0;
351 sal_Int16 nTint = 0;
352 sal_Int16 nShade = 0;
354 for (auto const& rTransform : rComplexColor.getTransformations())
356 if (rTransform.meType == model::TransformationType::LumMod)
357 nLumMod = rTransform.mnValue;
358 if (rTransform.meType == model::TransformationType::LumOff)
359 nLumOff = rTransform.mnValue;
360 if (rTransform.meType == model::TransformationType::Tint)
361 nTint = rTransform.mnValue;
362 if (rTransform.meType == model::TransformationType::Shade)
363 nShade = rTransform.mnValue;
365 if (nLumMod == 10'000 && nLumOff == 0)
367 if (nTint != 0)
369 // Convert from 0-100 into 0-255
370 sal_Int16 nTint255 = std::round(255.0 - (double(nTint) / 10000.0) * 255.0);
371 DocxAttributeOutput::AddToAttrList(pAttrList, FSNS(XML_w, nThemeTintAttrId), OString::number(nTint255, 16));
373 else if (nShade != 0)
375 // Convert from 0-100 into 0-255
376 sal_Int16 nShade255 = std::round(255.0 - (double(nShade) / 10000.0) * 255.0);
377 DocxAttributeOutput::AddToAttrList(pAttrList, FSNS(XML_w, nThemeShadeAttrId), OString::number(nShade255, 16));
380 else
382 double nPercentage = 0.0;
384 if (nLumOff > 0)
385 nPercentage = double(nLumOff) / 100.0;
386 else
387 nPercentage = (-10'000 + double(nLumMod)) / 100.0;
389 // Convert from 0-100 into 0-255
390 sal_Int16 nTintShade255 = std::round(255.0 - (std::abs(nPercentage) / 100.0) * 255.0);
392 if (nPercentage > 0)
393 DocxAttributeOutput::AddToAttrList(pAttrList, FSNS(XML_w, nThemeTintAttrId), OString::number(nTintShade255, 16));
394 else if (nPercentage < 0)
395 DocxAttributeOutput::AddToAttrList(pAttrList, FSNS(XML_w, nThemeShadeAttrId), OString::number(nTintShade255, 16));
400 void lclAddThemeFillColorAttributes(rtl::Reference<sax_fastparser::FastAttributeList>& pAttrList, model::ComplexColor const& rComplexColor)
402 lclAddThemeValuesToCustomAttributes(pAttrList, rComplexColor, XML_themeFill, XML_themeFillTint, XML_themeFillShade);
405 void lclAddThemeColorAttributes(rtl::Reference<sax_fastparser::FastAttributeList>& pAttrList, model::ComplexColor const& rComplexColor)
407 lclAddThemeValuesToCustomAttributes(pAttrList, rComplexColor, XML_themeColor, XML_themeTint, XML_themeShade);
410 bool lclHasSolidFillTransformations(const model::ComplexColor& aComplexColor)
412 const std::vector<model::Transformation>& transformations = aComplexColor.getTransformations();
413 auto idx = std::find_if(transformations.begin(), transformations.end(), [](const model::Transformation& transformation) {
414 return transformation.meType != model::TransformationType::Shade && transformation.meType != model::TransformationType::Tint;
416 return idx != transformations.end();
419 } // end anonymous namespace
421 void DocxAttributeOutput::RTLAndCJKState( bool bIsRTL, sal_uInt16 /*nScript*/ )
423 if (bIsRTL)
424 m_pSerializer->singleElementNS(XML_w, XML_rtl, FSNS(XML_w, XML_val), "true");
427 /// Are multiple paragraphs disallowed inside this type of SDT?
428 static bool lcl_isOnelinerSdt(std::u16string_view rName)
430 return rName == u"Title" || rName == u"Subtitle" || rName == u"Company";
433 // write a floating table directly to docx without the surrounding frame
434 void DocxAttributeOutput::WriteFloatingTable(ww8::Frame const* pParentFrame)
436 const SwFrameFormat& rFrameFormat = pParentFrame->GetFrameFormat();
437 m_aFloatingTablesOfParagraph.insert(&rFrameFormat);
438 const SwNodeIndex* pNodeIndex = rFrameFormat.GetContent().GetContentIdx();
440 SwNodeOffset nStt = pNodeIndex ? pNodeIndex->GetIndex() + 1 : SwNodeOffset(0);
441 SwNodeOffset nEnd = pNodeIndex ? pNodeIndex->GetNode().EndOfSectionIndex() : SwNodeOffset(0);
443 //Save data here and restore when out of scope
444 ExportDataSaveRestore aDataGuard(GetExport(), nStt, nEnd, pParentFrame);
446 // Stash away info about the current table, so m_tableReference is clean.
447 DocxTableExportContext aTableExportContext(*this);
449 // set a floatingTableFrame AND unset parent frame,
450 // otherwise exporter thinks we are still in a frame
451 m_rExport.SetFloatingTableFrame(pParentFrame);
452 m_rExport.m_pParentFrame = nullptr;
454 GetExport().WriteText();
456 m_rExport.SetFloatingTableFrame(nullptr);
459 static void checkAndWriteFloatingTables(DocxAttributeOutput& rDocxAttributeOutput)
461 const auto& rExport = rDocxAttributeOutput.GetExport();
462 // iterate though all SpzFrameFormats and check whether they are anchored to the current text node
463 for( sal_uInt16 nCnt = rExport.m_rDoc.GetSpzFrameFormats()->size(); nCnt; )
465 const SwFrameFormat* pFrameFormat = (*rExport.m_rDoc.GetSpzFrameFormats())[ --nCnt ];
466 const SwFormatAnchor& rAnchor = pFrameFormat->GetAnchor();
467 const SwNode* pAnchorNode = rAnchor.GetAnchorNode();
469 if (!pAnchorNode || ! rExport.m_pCurPam->GetPointNode().GetTextNode())
470 continue;
472 if (*pAnchorNode != *rExport.m_pCurPam->GetPointNode().GetTextNode())
473 continue;
475 const SwNodeIndex* pStartNode = pFrameFormat->GetContent().GetContentIdx();
476 if (!pStartNode)
477 continue;
479 SwNodeIndex aStartNode = *pStartNode;
481 // go to the next node (actual content)
482 ++aStartNode;
484 // this has to be a table
485 if (!aStartNode.GetNode().IsTableNode())
486 continue;
488 // go to the end of the table
489 SwNodeOffset aEndIndex = aStartNode.GetNode().EndOfSectionIndex();
490 // go one deeper
491 aEndIndex++;
492 // this has to be the end of the content
493 if (aEndIndex != pFrameFormat->GetContent().GetContentIdx()->GetNode().EndOfSectionIndex())
494 continue;
496 // check for a grabBag and "TablePosition" attribute -> then we can export the table directly
497 SwTableNode* pTableNode = aStartNode.GetNode().GetTableNode();
498 SwTable& rTable = pTableNode->GetTable();
499 SwFrameFormat* pTableFormat = rTable.GetFrameFormat();
500 const SfxGrabBagItem* pTableGrabBag = pTableFormat->GetAttrSet().GetItem<SfxGrabBagItem>(RES_FRMATR_GRABBAG);
501 const std::map<OUString, css::uno::Any> & rTableGrabBag = pTableGrabBag->GetGrabBag();
502 // no grabbag?
503 if (rTableGrabBag.find(u"TablePosition"_ustr) == rTableGrabBag.end())
505 if (pFrameFormat->GetFlySplit().GetValue())
507 ww8::Frame aFrame(*pFrameFormat, *rAnchor.GetContentAnchor());
508 rDocxAttributeOutput.WriteFloatingTable(&aFrame);
510 continue;
513 // write table to docx
514 ww8::Frame aFrame(*pFrameFormat, *rAnchor.GetContentAnchor());
515 rDocxAttributeOutput.WriteFloatingTable(&aFrame);
519 sal_Int32 DocxAttributeOutput::StartParagraph(const ww8::WW8TableNodeInfo::Pointer_t& pTextNodeInfo,
520 bool bGenerateParaId)
522 // Paragraphs (in headers/footers/comments/frames etc) can start before another finishes.
523 // So a stack is needed to keep track of each paragraph's status separately.
524 // Complication: Word can't handle nested text boxes, so those need to be collected together.
525 if ( !m_aFramesOfParagraph.size() || !m_nTextFrameLevel )
526 m_aFramesOfParagraph.push(std::vector<ww8::Frame>());
528 if ( m_nColBreakStatus == COLBRK_POSTPONE )
529 m_nColBreakStatus = COLBRK_WRITE;
531 // Output table/table row/table cell starts if needed
532 if ( pTextNodeInfo )
534 // New cell/row?
535 if ( m_tableReference.m_nTableDepth > 0 && !m_tableReference.m_bTableCellOpen )
537 ww8::WW8TableNodeInfoInner::Pointer_t pDeepInner( pTextNodeInfo->getInnerForDepth( m_tableReference.m_nTableDepth ) );
538 if ( pDeepInner->getCell() == 0 )
539 StartTableRow( pDeepInner );
541 const sal_uInt32 nCell = pDeepInner->getCell();
542 const sal_uInt32 nRow = pDeepInner->getRow();
544 SyncNodelessCells(pDeepInner, nCell, nRow);
545 StartTableCell(pDeepInner, nCell, nRow);
548 sal_uInt32 nRow = pTextNodeInfo->getRow();
549 sal_uInt32 nCell = pTextNodeInfo->getCell();
550 if (nCell == 0)
552 // Do we have to start the table?
553 // [If we are at the right depth already, it means that we
554 // continue the table cell]
555 sal_uInt32 nCurrentDepth = pTextNodeInfo->getDepth();
557 if ( nCurrentDepth > m_tableReference.m_nTableDepth )
559 // Start all the tables that begin here
560 for ( sal_uInt32 nDepth = m_tableReference.m_nTableDepth + 1; nDepth <= nCurrentDepth; ++nDepth )
562 ww8::WW8TableNodeInfoInner::Pointer_t pInner( pTextNodeInfo->getInnerForDepth( nDepth ) );
564 StartTable( pInner );
565 StartTableRow( pInner );
567 StartTableCell(pInner, 0, nDepth == nCurrentDepth ? nRow : 0);
570 m_tableReference.m_nTableDepth = nCurrentDepth;
575 // look ahead for floating tables that were put into a frame during import
576 // floating tables in shapes are not supported: exclude this case
577 if (!m_rExport.SdrExporter().IsDMLAndVMLDrawingOpen())
579 // Do this after opening table/row/cell, so floating tables anchored at cell start go inside
580 // the cell, not outside.
581 checkAndWriteFloatingTables(*this);
584 // Look up the "sdt end before this paragraph" property early, when it
585 // would normally arrive, it would be too late (would be after the
586 // paragraph start has been written).
587 bool bEndParaSdt = false;
588 if (m_aParagraphSdt.m_bStartedSdt)
590 SwTextNode* pTextNode = m_rExport.m_pCurPam->GetPointNode().GetTextNode();
591 if (pTextNode && pTextNode->GetpSwAttrSet())
593 const SfxItemSet* pSet = pTextNode->GetpSwAttrSet();
594 if (const SfxPoolItem* pItem = pSet->GetItem(RES_PARATR_GRABBAG))
596 const SfxGrabBagItem& rParaGrabBag = static_cast<const SfxGrabBagItem&>(*pItem);
597 const std::map<OUString, css::uno::Any>& rMap = rParaGrabBag.GetGrabBag();
598 bEndParaSdt = m_aParagraphSdt.m_bStartedSdt && rMap.contains(u"ParaSdtEndBefore"_ustr);
602 // TODO also avoid multiline paragraphs in those SDT types for shape text
603 bool bOneliner = m_aParagraphSdt.m_bStartedSdt && !m_rExport.SdrExporter().IsDMLAndVMLDrawingOpen() && lcl_isOnelinerSdt(m_aStartedParagraphSdtPrAlias);
604 if (bEndParaSdt || (m_aParagraphSdt.m_bStartedSdt && m_bHadSectPr) || bOneliner)
606 // This is the common case: "close sdt before the current paragraph" was requested by the next paragraph.
607 m_aParagraphSdt.EndSdtBlock(m_pSerializer);
608 m_aStartedParagraphSdtPrAlias.clear();
610 m_bHadSectPr = false;
612 // this mark is used to be able to enclose the paragraph inside a sdr tag.
613 // We will only know if we have to do that later.
614 m_pSerializer->mark(Tag_StartParagraph_1);
616 std::optional<OUString> aParaId;
617 sal_Int32 nParaId = 0;
618 if (bGenerateParaId)
620 nParaId = m_nNextParaId++;
621 aParaId = NumberToHexBinary(nParaId);
623 m_pSerializer->startElementNS(XML_w, XML_p, FSNS(XML_w14, XML_paraId), aParaId);
625 // postpone the output of the run (we get it before the paragraph
626 // properties, but must write it after them)
627 m_pSerializer->mark(Tag_StartParagraph_2);
629 // no section break in this paragraph yet; can be set in SectionBreak()
630 m_pSectionInfo.reset();
632 m_bParagraphOpened = true;
633 m_bIsFirstParagraph = false;
634 m_nHyperLinkCount.push_back(0);
636 return nParaId;
639 OString DocxAttributeOutput::convertToOOXMLVertOrient(sal_Int16 nOrient)
641 switch( nOrient )
643 case text::VertOrientation::CENTER:
644 case text::VertOrientation::LINE_CENTER:
645 return "center"_ostr;
646 case text::VertOrientation::BOTTOM:
647 return "bottom"_ostr;
648 case text::VertOrientation::LINE_BOTTOM:
649 return "outside"_ostr;
650 case text::VertOrientation::TOP:
651 return "top"_ostr;
652 case text::VertOrientation::LINE_TOP:
653 return "inside"_ostr;
654 default:
655 return OString();
659 OString DocxAttributeOutput::convertToOOXMLHoriOrient(sal_Int16 nOrient, bool bIsPosToggle)
661 switch( nOrient )
663 case text::HoriOrientation::LEFT:
664 return bIsPosToggle ? "inside" : "left";
665 case text::HoriOrientation::INSIDE:
666 return "inside"_ostr;
667 case text::HoriOrientation::RIGHT:
668 return bIsPosToggle ? "outside" : "right";
669 case text::HoriOrientation::OUTSIDE:
670 return "outside"_ostr;
671 case text::HoriOrientation::CENTER:
672 case text::HoriOrientation::FULL:
673 return "center"_ostr;
674 default:
675 return OString();
679 OString DocxAttributeOutput::convertToOOXMLVertOrientRel(sal_Int16 nOrientRel)
681 switch (nOrientRel)
683 case text::RelOrientation::PAGE_PRINT_AREA:
684 return "margin"_ostr;
685 case text::RelOrientation::PAGE_FRAME:
686 return "page"_ostr;
687 case text::RelOrientation::FRAME:
688 case text::RelOrientation::TEXT_LINE:
689 default:
690 return "text"_ostr;
694 OString DocxAttributeOutput::convertToOOXMLHoriOrientRel(sal_Int16 nOrientRel)
696 switch (nOrientRel)
698 case text::RelOrientation::PAGE_PRINT_AREA:
699 return "margin"_ostr;
700 case text::RelOrientation::PAGE_FRAME:
701 return "page"_ostr;
702 case text::RelOrientation::CHAR:
703 case text::RelOrientation::PAGE_RIGHT:
704 case text::RelOrientation::FRAME:
705 default:
706 return "text"_ostr;
710 void FramePrHelper::SetFrame(ww8::Frame* pFrame, sal_Int32 nTableDepth)
712 assert(!pFrame || !m_pFrame);
713 m_pFrame = pFrame;
714 m_nTableDepth = nTableDepth;
715 if (m_pFrame)
717 m_bUseFrameBorders = true;
718 m_bUseFrameBackground = true;
719 m_bUseFrameTextDirection = true;
723 bool FramePrHelper::UseFrameBorders(sal_Int32 nTableDepth)
725 if (!m_pFrame || m_nTableDepth < nTableDepth)
726 return false;
728 return m_bUseFrameBorders;
731 bool FramePrHelper::UseFrameBackground()
733 if (!m_pFrame)
734 return false;
736 return m_bUseFrameBackground;
739 bool FramePrHelper::UseFrameTextDirection(sal_Int32 nTableDepth)
741 if (!m_pFrame || m_nTableDepth < nTableDepth)
742 return false;
744 return m_bUseFrameTextDirection;
747 void SdtBlockHelper::DeleteAndResetTheLists()
749 if (m_pTokenChildren.is() )
750 m_pTokenChildren.clear();
751 if (m_pDataBindingAttrs.is() )
752 m_pDataBindingAttrs.clear();
753 if (m_pTextAttrs.is())
754 m_pTextAttrs.clear();
755 if (!m_aAlias.isEmpty())
756 m_aAlias.clear();
757 if (!m_aTag.isEmpty())
758 m_aTag.clear();
759 if (!m_aLock.isEmpty())
760 m_aLock.clear();
761 if (!m_aPlaceHolderDocPart.isEmpty())
762 m_aPlaceHolderDocPart.clear();
763 if (!m_aColor.isEmpty())
764 m_aColor.clear();
765 if (!m_aAppearance.isEmpty())
766 m_aAppearance.clear();
767 m_bShowingPlaceHolder = false;
768 m_nId = 0;
769 m_nTabIndex = 0;
772 void SdtBlockHelper::WriteSdtBlock(const ::sax_fastparser::FSHelperPtr& pSerializer, bool bRunTextIsOn, bool bParagraphHasDrawing)
774 if (m_nSdtPrToken <= 0 && !m_pDataBindingAttrs.is() && !m_nId)
775 return;
777 // sdt start mark
778 pSerializer->mark(DocxAttributeOutput::Tag_WriteSdtBlock);
780 pSerializer->startElementNS(XML_w, XML_sdt);
782 // output sdt properties
783 pSerializer->startElementNS(XML_w, XML_sdtPr);
785 if (m_nSdtPrToken > 0 && m_pTokenChildren.is())
787 if (!m_pTokenAttributes.is())
788 pSerializer->startElement(m_nSdtPrToken);
789 else
791 pSerializer->startElement(m_nSdtPrToken, detachFrom(m_pTokenAttributes));
794 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)) {
795 const uno::Sequence<xml::FastAttribute> aChildren = m_pTokenChildren->getFastAttributes();
796 for (const auto& rChild : aChildren)
797 pSerializer->singleElement(rChild.Token, FSNS(XML_w, XML_val), rChild.Value);
800 pSerializer->endElement(m_nSdtPrToken);
802 else if ((m_nSdtPrToken > 0) && m_nSdtPrToken != FSNS(XML_w, XML_id) && !(bRunTextIsOn && bParagraphHasDrawing))
804 if (!m_pTokenAttributes.is())
805 pSerializer->singleElement(m_nSdtPrToken);
806 else
808 pSerializer->singleElement(m_nSdtPrToken, detachFrom(m_pTokenAttributes));
812 WriteExtraParams(pSerializer);
814 pSerializer->endElementNS(XML_w, XML_sdtPr);
816 // sdt contents start tag
817 pSerializer->startElementNS(XML_w, XML_sdtContent);
819 // prepend the tags since the sdt start mark before the paragraph
820 pSerializer->mergeTopMarks(DocxAttributeOutput::Tag_WriteSdtBlock, sax_fastparser::MergeMarks::PREPEND);
822 // write the ending tags after the paragraph
823 m_bStartedSdt = true;
825 // clear sdt status
826 m_nSdtPrToken = 0;
827 DeleteAndResetTheLists();
830 void SdtBlockHelper::WriteExtraParams(const ::sax_fastparser::FSHelperPtr& pSerializer)
832 if (m_nId)
834 pSerializer->singleElementNS(XML_w, XML_id, FSNS(XML_w, XML_val), OString::number(m_nId));
837 if (m_pDataBindingAttrs.is())
839 pSerializer->singleElementNS(XML_w, XML_dataBinding, detachFrom(m_pDataBindingAttrs));
842 if (m_pTextAttrs.is())
844 pSerializer->singleElementNS(XML_w, XML_text, detachFrom(m_pTextAttrs));
847 if (!m_aPlaceHolderDocPart.isEmpty())
849 pSerializer->startElementNS(XML_w, XML_placeholder);
850 pSerializer->singleElementNS(XML_w, XML_docPart, FSNS(XML_w, XML_val), m_aPlaceHolderDocPart);
851 pSerializer->endElementNS(XML_w, XML_placeholder);
854 if (m_bShowingPlaceHolder)
855 pSerializer->singleElementNS(XML_w, XML_showingPlcHdr);
857 if (!m_aColor.isEmpty())
859 pSerializer->singleElementNS(XML_w15, XML_color, FSNS(XML_w, XML_val), m_aColor);
862 if (!m_aAppearance.isEmpty())
864 pSerializer->singleElementNS(XML_w15, XML_appearance, FSNS(XML_w15, XML_val), m_aAppearance);
867 if (!m_aAlias.isEmpty())
868 pSerializer->singleElementNS(XML_w, XML_alias, FSNS(XML_w, XML_val), m_aAlias);
870 if (!m_aTag.isEmpty())
871 pSerializer->singleElementNS(XML_w, XML_tag, FSNS(XML_w, XML_val), m_aTag);
873 if (m_nTabIndex)
874 pSerializer->singleElementNS(XML_w, XML_tabIndex, FSNS(XML_w, XML_val),
875 OString::number(m_nTabIndex));
877 if (!m_aLock.isEmpty())
878 pSerializer->singleElementNS(XML_w, XML_lock, FSNS(XML_w, XML_val), m_aLock);
881 void SdtBlockHelper::EndSdtBlock(const ::sax_fastparser::FSHelperPtr& pSerializer)
883 pSerializer->endElementNS(XML_w, XML_sdtContent);
884 pSerializer->endElementNS(XML_w, XML_sdt);
885 m_bStartedSdt = false;
888 void SdtBlockHelper::GetSdtParamsFromGrabBag(const uno::Sequence<beans::PropertyValue>& aGrabBagSdt)
890 for (const beans::PropertyValue& aPropertyValue : aGrabBagSdt)
892 if (aPropertyValue.Name == "ooxml:CT_SdtPr_checkbox")
894 m_nSdtPrToken = FSNS(XML_w14, XML_checkbox);
895 uno::Sequence<beans::PropertyValue> aGrabBag;
896 aPropertyValue.Value >>= aGrabBag;
897 for (const auto& rProp : aGrabBag)
899 if (rProp.Name == "ooxml:CT_SdtCheckbox_checked")
900 DocxAttributeOutput::AddToAttrList(m_pTokenChildren,
901 FSNS(XML_w14, XML_checked), rProp.Value.get<OUString>());
902 else if (rProp.Name == "ooxml:CT_SdtCheckbox_checkedState")
903 DocxAttributeOutput::AddToAttrList(m_pTokenChildren,
904 FSNS(XML_w14, XML_checkedState), rProp.Value.get<OUString>());
905 else if (rProp.Name == "ooxml:CT_SdtCheckbox_uncheckedState")
906 DocxAttributeOutput::AddToAttrList(m_pTokenChildren,
907 FSNS(XML_w14, XML_uncheckedState), rProp.Value.get<OUString>());
910 else if (aPropertyValue.Name == "ooxml:CT_SdtPr_dataBinding" && !m_pDataBindingAttrs.is())
912 uno::Sequence<beans::PropertyValue> aGrabBag;
913 aPropertyValue.Value >>= aGrabBag;
914 for (const auto& rProp : aGrabBag)
916 if (rProp.Name == "ooxml:CT_DataBinding_prefixMappings")
917 DocxAttributeOutput::AddToAttrList( m_pDataBindingAttrs,
918 FSNS( XML_w, XML_prefixMappings ), rProp.Value.get<OUString>());
919 else if (rProp.Name == "ooxml:CT_DataBinding_xpath")
920 DocxAttributeOutput::AddToAttrList( m_pDataBindingAttrs,
921 FSNS( XML_w, XML_xpath ), rProp.Value.get<OUString>());
922 else if (rProp.Name == "ooxml:CT_DataBinding_storeItemID")
923 DocxAttributeOutput::AddToAttrList( m_pDataBindingAttrs,
924 FSNS( XML_w, XML_storeItemID ), rProp.Value.get<OUString>());
927 else if (aPropertyValue.Name == "ooxml:CT_SdtPr_text")
929 uno::Sequence<beans::PropertyValue> aGrabBag;
930 aPropertyValue.Value >>= aGrabBag;
931 if (aGrabBag.hasElements())
933 for (const auto& rProp : aGrabBag)
935 if (rProp.Name == "ooxml:CT_SdtText_multiLine")
936 DocxAttributeOutput::AddToAttrList(m_pTextAttrs,
937 FSNS(XML_w, XML_multiLine), rProp.Value.get<OUString>());
940 else
942 // We still have w:text, but no attrs
943 m_nSdtPrToken = FSNS(XML_w, XML_text);
946 else if (aPropertyValue.Name == "ooxml:CT_SdtPlaceholder_docPart")
948 uno::Sequence<beans::PropertyValue> aGrabBag;
949 aPropertyValue.Value >>= aGrabBag;
950 for (const auto& rProp : aGrabBag)
952 if (rProp.Name == "ooxml:CT_SdtPlaceholder_docPart_val")
953 m_aPlaceHolderDocPart = rProp.Value.get<OUString>();
956 else if (aPropertyValue.Name == "ooxml:CT_SdtPr_color")
958 uno::Sequence<beans::PropertyValue> aGrabBag;
959 aPropertyValue.Value >>= aGrabBag;
960 for (const auto& rProp : aGrabBag)
962 if (rProp.Name == "ooxml:CT_SdtColor_val")
963 m_aColor = rProp.Value.get<OUString>();
966 else if (aPropertyValue.Name == "ooxml:CT_SdtPr_appearance")
968 if (!(aPropertyValue.Value >>= m_aAppearance))
969 SAL_WARN("sw.ww8", "DocxAttributeOutput::GrabBag: unexpected sdt appearance value");
971 else if (aPropertyValue.Name == "ooxml:CT_SdtPr_showingPlcHdr")
973 if (!(aPropertyValue.Value >>= m_bShowingPlaceHolder))
974 SAL_WARN("sw.ww8", "DocxAttributeOutput::GrabBag: unexpected sdt ShowingPlcHdr");
976 else if (aPropertyValue.Name == "ooxml:CT_SdtPr_alias" && m_aAlias.isEmpty())
978 if (!(aPropertyValue.Value >>= m_aAlias))
979 SAL_WARN("sw.ww8", "DocxAttributeOutput::GrabBag: unexpected sdt alias value");
981 else if (aPropertyValue.Name == "ooxml:CT_SdtPr_tag" && m_aTag.isEmpty())
983 if (!(aPropertyValue.Value >>= m_aTag))
984 SAL_WARN("sw.ww8", "DocxAttributeOutput::GrabBag: unexpected sdt tag value");
986 else if (aPropertyValue.Name == "ooxml:CT_SdtPr_id")
988 if (!(aPropertyValue.Value >>= m_nId))
989 SAL_WARN("sw.ww8", "DocxAttributeOutput::GrabBag: unexpected sdt id value");
991 else if (aPropertyValue.Name == "ooxml:CT_SdtPr_tabIndex" && !m_nTabIndex)
993 if (!(aPropertyValue.Value >>= m_nTabIndex))
994 SAL_WARN("sw.ww8", "DocxAttributeOutput::GrabBag: unexpected sdt tabIndex value");
996 else if (aPropertyValue.Name == "ooxml:CT_SdtPr_lock" && m_aLock.isEmpty())
998 if (!(aPropertyValue.Value >>= m_aLock))
999 SAL_WARN("sw.ww8", "DocxAttributeOutput::GrabBag: unexpected sdt lock value");
1001 else if (aPropertyValue.Name == "ooxml:CT_SdtPr_citation")
1002 m_nSdtPrToken = FSNS(XML_w, XML_citation);
1003 else if (aPropertyValue.Name == "ooxml:CT_SdtPr_docPartObj" ||
1004 aPropertyValue.Name == "ooxml:CT_SdtPr_docPartList")
1006 if (aPropertyValue.Name == "ooxml:CT_SdtPr_docPartObj")
1007 m_nSdtPrToken = FSNS(XML_w, XML_docPartObj);
1008 else if (aPropertyValue.Name == "ooxml:CT_SdtPr_docPartList")
1009 m_nSdtPrToken = FSNS(XML_w, XML_docPartList);
1011 uno::Sequence<beans::PropertyValue> aGrabBag;
1012 aPropertyValue.Value >>= aGrabBag;
1013 for (const auto& rProp : aGrabBag)
1015 if (rProp.Name == "ooxml:CT_SdtDocPart_docPartGallery")
1016 DocxAttributeOutput::AddToAttrList(m_pTokenChildren,
1017 FSNS(XML_w, XML_docPartGallery), rProp.Value.get<OUString>());
1018 else if (rProp.Name == "ooxml:CT_SdtDocPart_docPartCategory")
1019 DocxAttributeOutput::AddToAttrList(m_pTokenChildren,
1020 FSNS(XML_w, XML_docPartCategory), rProp.Value.get<OUString>());
1021 else if (rProp.Name == "ooxml:CT_SdtDocPart_docPartUnique")
1023 OUString sValue = rProp.Value.get<OUString>();
1024 if (sValue.isEmpty())
1025 sValue = "true";
1026 DocxAttributeOutput::AddToAttrList(m_pTokenChildren, FSNS(XML_w, XML_docPartUnique),
1027 sValue);
1031 else if (aPropertyValue.Name == "ooxml:CT_SdtPr_equation")
1032 m_nSdtPrToken = FSNS(XML_w, XML_equation);
1033 else if (aPropertyValue.Name == "ooxml:CT_SdtPr_picture")
1034 m_nSdtPrToken = FSNS(XML_w, XML_picture);
1035 else if (aPropertyValue.Name == "ooxml:CT_SdtPr_group")
1036 m_nSdtPrToken = FSNS(XML_w, XML_group);
1037 else
1038 SAL_WARN("sw.ww8", "GetSdtParamsFromGrabBag unhandled SdtPr grab bag property " << aPropertyValue.Name);
1042 void DocxAttributeOutput::PopulateFrameProperties(const SwFrameFormat* pFrameFormat, const Size& rSize)
1044 rtl::Reference<sax_fastparser::FastAttributeList> attrList = FastSerializerHelper::createAttrList();
1046 const SwFormatHoriOrient& rHoriOrient = pFrameFormat->GetHoriOrient();
1047 const SwFormatVertOrient& rVertOrient = pFrameFormat->GetVertOrient();
1048 awt::Point aPos(rHoriOrient.GetPos(), rVertOrient.GetPos());
1050 // A few assumptions need to be made here, because framePr is a confused mixture
1051 // of (multiple) paragraph's border properties being transferred to/from a frame.
1052 // The frame size describes the size BEFORE the PARAGRAPH border spacing is applied.
1053 // However, we can't actually look at all the paragraphs' borders because they might be
1054 // different, and all MUST specify the same frame width in order to belong to the same frame.
1055 // In order for them all to be consistent, the only choice is to use the frame's border spacing.
1056 // During import, the frame was assigned border spacing based on the contained paragraphs.
1057 // So now at export time we have to assume that none of this has been changed by the user.
1059 // 620 (31pt) is the maximum paragraph border spacing allowed in MS Formats,
1060 // so if the value is greater than that, avoid adjusting the size - the user has interfered.
1061 const sal_uInt32 nLeftBorderSpacing = pFrameFormat->GetBox().GetDistance(SvxBoxItemLine::LEFT);
1062 const sal_uInt32 nRighttBorderSpacing = pFrameFormat->GetBox().GetDistance(SvxBoxItemLine::RIGHT);
1063 sal_uInt32 nAdjustedWidth = rSize.Width();
1064 if (nLeftBorderSpacing < 621 && nRighttBorderSpacing < 621
1065 && nAdjustedWidth > nLeftBorderSpacing + nRighttBorderSpacing)
1067 nAdjustedWidth -= nLeftBorderSpacing + nRighttBorderSpacing;
1069 attrList->add( FSNS( XML_w, XML_w), OString::number(nAdjustedWidth));
1070 attrList->add( FSNS( XML_w, XML_h), OString::number(rSize.Height()));
1072 const OString relativeFromH = convertToOOXMLHoriOrientRel(rHoriOrient.GetRelationOrient());
1073 const OString relativeFromV = convertToOOXMLVertOrientRel(rVertOrient.GetRelationOrient());
1074 OString aXAlign = convertToOOXMLHoriOrient(rHoriOrient.GetHoriOrient(), /*bIsPosToggle=*/false);
1075 OString aYAlign = convertToOOXMLVertOrient(rVertOrient.GetVertOrient());
1076 if (!aXAlign.isEmpty())
1077 attrList->add(FSNS(XML_w, XML_xAlign), aXAlign);
1078 else if (aPos.X)
1079 attrList->add( FSNS( XML_w, XML_x), OString::number(aPos.X));
1080 if (!aYAlign.isEmpty() && relativeFromV != "text")
1081 attrList->add(FSNS(XML_w, XML_yAlign), aYAlign);
1082 else if (aPos.Y)
1083 attrList->add( FSNS( XML_w, XML_y), OString::number(aPos.Y));
1085 sal_Int16 nLeft = pFrameFormat->GetLRSpace().ResolveLeft({});
1086 sal_Int16 nRight = pFrameFormat->GetLRSpace().ResolveRight({});
1087 sal_Int16 nUpper = pFrameFormat->GetULSpace().GetUpper();
1088 sal_Int16 nLower = pFrameFormat->GetULSpace().GetLower();
1090 // To emulate, on import left was ignored (set to zero) if aligned to left,
1091 // so just double up the right spacing in order to prevent cutting in half each round-trip.
1092 if (rHoriOrient.GetHoriOrient() == text::HoriOrientation::LEFT)
1093 nLeft = nRight;
1094 else if (rHoriOrient.GetHoriOrient() == text::HoriOrientation::RIGHT)
1095 nRight = nLeft;
1097 attrList->add(FSNS(XML_w, XML_hSpace), OString::number((nLeft + nRight) / 2));
1098 attrList->add(FSNS(XML_w, XML_vSpace), OString::number((nUpper + nLower) / 2));
1100 switch (pFrameFormat->GetSurround().GetValue())
1102 case css::text::WrapTextMode_NONE:
1103 attrList->add( FSNS( XML_w, XML_wrap), "notBeside");
1104 break;
1105 case css::text::WrapTextMode_DYNAMIC:
1106 attrList->add(FSNS(XML_w, XML_wrap), "auto");
1107 break;
1108 case css::text::WrapTextMode_PARALLEL:
1109 default:
1110 attrList->add(FSNS(XML_w, XML_wrap), "around");
1111 break;
1113 attrList->add( FSNS( XML_w, XML_vAnchor), relativeFromV );
1114 attrList->add( FSNS( XML_w, XML_hAnchor), relativeFromH );
1115 attrList->add( FSNS( XML_w, XML_hRule), "exact");
1117 m_pSerializer->singleElementNS( XML_w, XML_framePr, attrList );
1120 void DocxAttributeOutput::EndParagraph( const ww8::WW8TableNodeInfoInner::Pointer_t& pTextNodeInfoInner )
1122 // write the paragraph properties + the run, already in the correct order
1123 m_pSerializer->mergeTopMarks(Tag_StartParagraph_2);
1124 std::vector< std::shared_ptr <ww8::Frame> > aFramePrTextbox;
1125 // Write the anchored frame if any
1126 // Word can't handle nested text boxes, so write them on the same level.
1127 ++m_nTextFrameLevel;
1128 if( m_nTextFrameLevel == 1 && !m_rExport.SdrExporter().IsDMLAndVMLDrawingOpen() )
1130 comphelper::FlagRestorationGuard aStartedParaSdtGuard(m_aParagraphSdt.m_bStartedSdt, false);
1132 assert(!m_oPostponedCustomShape);
1133 m_oPostponedCustomShape.emplace();
1135 // The for loop can change the size of m_aFramesOfParagraph, so the max size cannot be set in stone before the loop.
1136 size_t nFrames = m_aFramesOfParagraph.size() ? m_aFramesOfParagraph.top().size() : 0;
1137 for (size_t nIndex = 0; nIndex < nFrames; ++nIndex)
1139 m_bParagraphFrameOpen = true;
1140 ww8::Frame aFrame = m_aFramesOfParagraph.top()[nIndex];
1141 const SwFrameFormat& rFrameFormat = aFrame.GetFrameFormat();
1143 if (!m_bWritingHeaderFooter && SwTextBoxHelper::TextBoxIsFramePr(rFrameFormat))
1145 std::shared_ptr<ww8::Frame> pFramePr = std::make_shared<ww8::Frame>(aFrame);
1146 aFramePrTextbox.push_back(pFramePr);
1148 else
1150 if (m_aRunSdt.m_bStartedSdt)
1152 // Run-level SDT still open? Close it before AlternateContent.
1153 m_aRunSdt.EndSdtBlock(m_pSerializer);
1155 m_pSerializer->startElementNS(XML_w, XML_r);
1156 m_pSerializer->startElementNS(XML_mc, XML_AlternateContent);
1157 m_pSerializer->startElementNS(XML_mc, XML_Choice, XML_Requires, "wps");
1159 This is to avoid AlternateContent within another AlternateContent.
1160 So when Choice is Open, only write the DML Drawing instead of both DML
1161 and VML Drawing in another AlternateContent.
1163 SetAlternateContentChoiceOpen( true );
1164 /** Save the table info's before writing the shape
1165 as there might be a new table that might get
1166 spawned from within the VML & DML block and alter
1167 the contents.
1169 ww8::WW8TableInfo::Pointer_t xOldTableInfo = m_rExport.m_pTableInfo;
1170 //Reset the table infos after saving.
1171 m_rExport.m_pTableInfo = std::make_shared<ww8::WW8TableInfo>();
1173 /** FDO#71834 :
1174 Save the table reference attributes before calling WriteDMLTextFrame,
1175 otherwise the StartParagraph function will use the previous existing
1176 table reference attributes since the variable is being shared.
1179 DocxTableExportContext aDMLTableExportContext(*this);
1180 m_rExport.SdrExporter().writeDMLTextFrame(&aFrame, m_anchorId++);
1182 m_pSerializer->endElementNS(XML_mc, XML_Choice);
1183 SetAlternateContentChoiceOpen( false );
1185 // Reset table infos, otherwise the depth of the cells will be incorrect,
1186 // in case the text frame had table(s) and we try to export the
1187 // same table second time.
1188 m_rExport.m_pTableInfo = std::make_shared<ww8::WW8TableInfo>();
1189 //reset the tableReference.
1191 m_pSerializer->startElementNS(XML_mc, XML_Fallback);
1193 DocxTableExportContext aVMLTableExportContext(*this);
1194 m_rExport.SdrExporter().writeVMLTextFrame(&aFrame);
1196 m_rExport.m_pTableInfo = std::move(xOldTableInfo);
1198 m_pSerializer->endElementNS(XML_mc, XML_Fallback);
1199 m_pSerializer->endElementNS(XML_mc, XML_AlternateContent);
1200 m_pSerializer->endElementNS( XML_w, XML_r );
1201 m_bParagraphFrameOpen = false;
1204 nFrames = m_aFramesOfParagraph.size() ? m_aFramesOfParagraph.top().size() : 0;
1206 if (!m_oPostponedCustomShape->empty())
1208 m_pSerializer->startElementNS(XML_w, XML_r);
1209 WritePostponedCustomShape();
1210 m_pSerializer->endElementNS( XML_w, XML_r );
1212 m_oPostponedCustomShape.reset();
1214 if ( m_aFramesOfParagraph.size() )
1215 m_aFramesOfParagraph.top().clear();
1217 if (!pTextNodeInfoInner)
1219 // Ending a non-table paragraph, clear floating tables before paragraph.
1220 m_aFloatingTablesOfParagraph.clear();
1224 --m_nTextFrameLevel;
1225 if ( m_aFramesOfParagraph.size() && !m_nTextFrameLevel )
1226 m_aFramesOfParagraph.pop();
1228 /* If m_nHyperLinkCount > 0 that means hyperlink tag is not yet closed.
1229 * This is due to nested hyperlink tags. So close it before end of paragraph.
1231 if(m_nHyperLinkCount.back() > 0)
1233 for(sal_Int32 nHyperLinkToClose = 0; nHyperLinkToClose < m_nHyperLinkCount.back(); ++nHyperLinkToClose)
1234 m_pSerializer->endElementNS( XML_w, XML_hyperlink );
1236 m_nHyperLinkCount.pop_back();
1238 if (m_aRunSdt.m_bStartedSdt)
1240 // Run-level SDT still open? Close it now.
1241 m_aRunSdt.EndSdtBlock(m_pSerializer);
1244 if (m_bPageBreakAfter)
1246 // tdf#128889 Trailing page break
1247 SectionBreak(msword::PageBreak, false);
1248 m_bPageBreakAfter = false;
1251 m_pSerializer->endElementNS( XML_w, XML_p );
1252 // on export sdt blocks are never nested ATM
1253 if (!m_bAnchorLinkedToNode && !m_aParagraphSdt.m_bStartedSdt)
1255 m_aParagraphSdt.WriteSdtBlock(m_pSerializer, m_bRunTextIsOn, m_rExport.SdrExporter().IsParagraphHasDrawing());
1257 if (m_aParagraphSdt.m_bStartedSdt)
1259 if (m_tableReference.m_bTableCellOpen)
1260 m_tableReference.m_bTableCellParaSdtOpen = true;
1261 if (m_rExport.SdrExporter().IsDMLAndVMLDrawingOpen())
1262 m_rExport.SdrExporter().setParagraphSdtOpen(true);
1265 else
1267 //These should be written out to the actual Node and not to the anchor.
1268 //Clear them as they will be repopulated when the node is processed.
1269 m_aParagraphSdt.m_nSdtPrToken = 0;
1270 m_aParagraphSdt.DeleteAndResetTheLists();
1273 m_pSerializer->mark(Tag_StartParagraph_2);
1275 // Write framePr
1276 for ( const auto & pFrame : aFramePrTextbox )
1278 DocxTableExportContext aTableExportContext(*this);
1279 m_aFramePr.SetFrame(pFrame.get(), !m_xTableWrt ? -1 : m_tableReference.m_nTableDepth);
1280 m_rExport.SdrExporter().writeOnlyTextOfFrame(pFrame.get());
1281 m_aFramePr.SetFrame(nullptr);
1284 m_pSerializer->mergeTopMarks(Tag_StartParagraph_2, sax_fastparser::MergeMarks::PREPEND);
1286 //sdtcontent is written so Set m_bParagraphHasDrawing to false
1287 m_rExport.SdrExporter().setParagraphHasDrawing(false);
1288 m_bRunTextIsOn = false;
1289 m_pSerializer->mergeTopMarks(Tag_StartParagraph_1);
1291 aFramePrTextbox.clear();
1292 // Check for end of cell, rows, tables here
1293 FinishTableRowCell( pTextNodeInfoInner );
1295 if( !m_rExport.SdrExporter().IsDMLAndVMLDrawingOpen() )
1296 m_bParagraphOpened = false;
1298 // Clear bookmarks of the current paragraph
1299 m_aBookmarksOfParagraphStart.clear();
1300 m_aBookmarksOfParagraphEnd.clear();
1303 #define MAX_CELL_IN_WORD 62
1305 void DocxAttributeOutput::SyncNodelessCells(ww8::WW8TableNodeInfoInner::Pointer_t const & pInner, sal_Int32 nCell, sal_uInt32 nRow)
1307 sal_Int32 nOpenCell = m_LastOpenCell.back();
1308 if (nOpenCell != -1 && nOpenCell != nCell && nOpenCell < MAX_CELL_IN_WORD)
1309 EndTableCell(nOpenCell);
1311 sal_Int32 nClosedCell = m_LastClosedCell.back();
1312 for (sal_Int32 i = nClosedCell+1; i < nCell; ++i)
1314 if (i >= MAX_CELL_IN_WORD)
1315 break;
1317 if (i == 0)
1318 StartTableRow(pInner);
1320 StartTableCell(pInner, i, nRow);
1321 m_pSerializer->singleElementNS(XML_w, XML_p);
1322 EndTableCell(i);
1326 void DocxAttributeOutput::FinishTableRowCell( ww8::WW8TableNodeInfoInner::Pointer_t const & pInner, bool bForceEmptyParagraph )
1328 if ( !pInner )
1329 return;
1331 // Where are we in the table
1332 sal_uInt32 nRow = pInner->getRow();
1333 sal_Int32 nCell = pInner->getCell();
1335 InitTableHelper( pInner );
1337 // HACK
1338 // msoffice seems to have an internal limitation of 63 columns for tables
1339 // and refuses to load .docx with more, even though the spec seems to allow that;
1340 // so simply if there are more columns, don't close the last one msoffice will handle
1341 // and merge the contents of the remaining ones into it (since we don't close the cell
1342 // here, following ones will not be opened)
1343 const bool limitWorkaround = (nCell >= MAX_CELL_IN_WORD && !pInner->isEndOfLine());
1344 const bool bEndCell = pInner->isEndOfCell() && !limitWorkaround;
1345 const bool bEndRow = pInner->isEndOfLine();
1347 if (bEndCell)
1349 while (pInner->getDepth() < m_tableReference.m_nTableDepth)
1351 //we expect that the higher depth row was closed, and
1352 //we are just missing the table close
1353 assert(m_LastOpenCell.back() == -1 && m_LastClosedCell.back() == -1);
1354 EndTable();
1357 SyncNodelessCells(pInner, nCell, nRow);
1359 sal_Int32 nClosedCell = m_LastClosedCell.back();
1360 if (nCell == nClosedCell)
1362 //Start missing trailing cell(s)
1363 ++nCell;
1364 StartTableCell(pInner, nCell, nRow);
1366 //Continue on missing next trailing cell(s)
1367 ww8::RowSpansPtr xRowSpans = pInner->getRowSpansOfRow();
1368 sal_Int32 nRemainingCells = xRowSpans->size() - nCell;
1369 for (sal_Int32 i = 1; i < nRemainingCells; ++i)
1371 if (bForceEmptyParagraph)
1373 m_pSerializer->singleElementNS(XML_w, XML_p);
1376 EndTableCell(nCell);
1378 StartTableCell(pInner, nCell, nRow);
1382 if (bForceEmptyParagraph)
1384 m_pSerializer->singleElementNS(XML_w, XML_p);
1387 EndTableCell(nCell);
1390 // This is a line end
1391 if (bEndRow)
1392 EndTableRow();
1394 // This is the end of the table
1395 if (pInner->isFinalEndOfLine())
1396 EndTable();
1399 void DocxAttributeOutput::EmptyParagraph()
1401 m_pSerializer->singleElementNS(XML_w, XML_p);
1404 void DocxAttributeOutput::SectionBreaks(const SwNode& rNode)
1406 // output page/section breaks
1407 // Writer can have them at the beginning of a paragraph, or at the end, but
1408 // in docx, we have to output them in the paragraph properties of the last
1409 // paragraph in a section. To get it right, we have to switch to the next
1410 // paragraph, and detect the section breaks there.
1411 SwNodeIndex aNextIndex( rNode, 1 );
1413 if (rNode.IsTextNode() || rNode.IsSectionNode())
1415 if (aNextIndex.GetNode().IsTextNode())
1417 const SwTextNode* pTextNode = static_cast<SwTextNode*>(&aNextIndex.GetNode());
1418 m_rExport.OutputSectionBreaks(pTextNode->GetpSwAttrSet(), *pTextNode, m_tableReference.m_bTableCellOpen);
1420 else if (aNextIndex.GetNode().IsTableNode())
1422 const SwTableNode* pTableNode = static_cast<SwTableNode*>(&aNextIndex.GetNode());
1423 const SwFrameFormat *pFormat = pTableNode->GetTable().GetFrameFormat();
1424 m_rExport.OutputSectionBreaks(&(pFormat->GetAttrSet()), *pTableNode);
1427 else if (rNode.IsEndNode())
1429 if (aNextIndex.GetNode().IsTextNode())
1431 // Handle section break between a table and a text node following it.
1432 // Also handle section endings
1433 const SwTextNode* pTextNode = aNextIndex.GetNode().GetTextNode();
1434 if (rNode.StartOfSectionNode()->IsTableNode() || rNode.StartOfSectionNode()->IsSectionNode())
1435 m_rExport.OutputSectionBreaks(pTextNode->GetpSwAttrSet(), *pTextNode, m_tableReference.m_bTableCellOpen);
1437 else if (aNextIndex.GetNode().IsTableNode())
1439 // Handle section break between tables.
1440 const SwTableNode* pTableNode = static_cast<SwTableNode*>(&aNextIndex.GetNode());
1441 const SwFrameFormat *pFormat = pTableNode->GetTable().GetFrameFormat();
1442 m_rExport.OutputSectionBreaks(&(pFormat->GetAttrSet()), *pTableNode);
1447 void DocxAttributeOutput::StartParagraphProperties()
1449 m_pSerializer->mark(Tag_StartParagraphProperties);
1451 m_pSerializer->startElementNS(XML_w, XML_pPr);
1452 m_bOpenedParaPr = true;
1454 // and output the section break now (if it appeared)
1455 if (m_pSectionInfo && m_rExport.m_nTextTyp == TXT_MAINTEXT)
1457 m_rExport.SectionProperties( *m_pSectionInfo );
1458 m_pSectionInfo.reset();
1461 InitCollectedParagraphProperties();
1464 void DocxAttributeOutput::InitCollectedParagraphProperties()
1466 m_pLRSpaceAttrList.clear();
1467 m_pParagraphSpacingAttrList.clear();
1469 // Write the elements in the spec order
1470 static const sal_Int32 aOrder[] =
1472 FSNS( XML_w, XML_pStyle ),
1473 FSNS( XML_w, XML_keepNext ),
1474 FSNS( XML_w, XML_keepLines ),
1475 FSNS( XML_w, XML_pageBreakBefore ),
1476 FSNS( XML_w, XML_framePr ),
1477 FSNS( XML_w, XML_widowControl ),
1478 FSNS( XML_w, XML_numPr ),
1479 FSNS( XML_w, XML_suppressLineNumbers ),
1480 FSNS( XML_w, XML_pBdr ),
1481 FSNS( XML_w, XML_shd ),
1482 FSNS( XML_w, XML_tabs ),
1483 FSNS( XML_w, XML_suppressAutoHyphens ),
1484 FSNS( XML_w, XML_kinsoku ),
1485 FSNS( XML_w, XML_wordWrap ),
1486 FSNS( XML_w, XML_overflowPunct ),
1487 FSNS( XML_w, XML_topLinePunct ),
1488 FSNS( XML_w, XML_autoSpaceDE ),
1489 FSNS( XML_w, XML_autoSpaceDN ),
1490 FSNS( XML_w, XML_bidi ),
1491 FSNS( XML_w, XML_adjustRightInd ),
1492 FSNS( XML_w, XML_snapToGrid ),
1493 FSNS( XML_w, XML_spacing ),
1494 FSNS( XML_w, XML_ind ),
1495 FSNS( XML_w, XML_contextualSpacing ),
1496 FSNS( XML_w, XML_mirrorIndents ),
1497 FSNS( XML_w, XML_suppressOverlap ),
1498 FSNS( XML_w, XML_jc ),
1499 FSNS( XML_w, XML_textDirection ),
1500 FSNS( XML_w, XML_textAlignment ),
1501 FSNS( XML_w, XML_textboxTightWrap ),
1502 FSNS( XML_w, XML_outlineLvl ),
1503 FSNS( XML_w, XML_divId ),
1504 FSNS( XML_w, XML_cnfStyle ),
1505 FSNS( XML_w, XML_rPr ),
1506 FSNS( XML_w, XML_sectPr ),
1507 FSNS( XML_w, XML_pPrChange )
1510 // postpone the output so that we can later [in EndParagraphProperties()]
1511 // prepend the properties before the run
1512 // coverity[overrun-buffer-arg : FALSE] - coverity has difficulty with css::uno::Sequence
1513 m_pSerializer->mark(Tag_InitCollectedParagraphProperties, comphelper::containerToSequence(aOrder));
1516 void DocxAttributeOutput::WriteCollectedParagraphProperties()
1518 if ( m_rExport.SdrExporter().getFlyAttrList().is() )
1520 m_pSerializer->singleElementNS( XML_w, XML_framePr,
1521 detachFrom(m_rExport.SdrExporter().getFlyAttrList() ) );
1524 if (m_pLRSpaceAttrList.is())
1526 m_pSerializer->singleElementNS(XML_w, XML_ind, detachFrom(m_pLRSpaceAttrList));
1529 if ( m_pParagraphSpacingAttrList.is() )
1531 m_pSerializer->singleElementNS( XML_w, XML_spacing, detachFrom( m_pParagraphSpacingAttrList ) );
1534 if ( m_pBackgroundAttrList.is() )
1536 m_pSerializer->singleElementNS( XML_w, XML_shd, detachFrom( m_pBackgroundAttrList ) );
1537 m_aFramePr.SetUseFrameBackground(false);
1541 namespace
1544 /// Outputs an item set, that contains the formatting of the paragraph marker.
1545 void lcl_writeParagraphMarkerProperties(DocxAttributeOutput& rAttributeOutput, const SfxItemSet& rParagraphMarkerProperties)
1547 const SfxItemSet* pOldI = rAttributeOutput.GetExport().GetCurItemSet();
1548 rAttributeOutput.GetExport().SetCurItemSet(&rParagraphMarkerProperties);
1550 SfxWhichIter aIter(rParagraphMarkerProperties);
1551 sal_uInt16 nWhichId = aIter.FirstWhich();
1552 const SfxPoolItem* pItem = nullptr;
1553 // Did we already produce a <w:sz> element?
1554 bool bFontSizeWritten = false;
1555 bool bBoldWritten = false;
1556 while (nWhichId)
1558 if (aIter.GetItemState(true, &pItem) == SfxItemState::SET)
1560 if (isCHRATR(nWhichId) || nWhichId == RES_TXTATR_CHARFMT)
1562 // Will this item produce a <w:sz> element?
1563 bool bFontSizeItem = nWhichId == RES_CHRATR_FONTSIZE || nWhichId == RES_CHRATR_CJK_FONTSIZE;
1564 bool bBoldItem = nWhichId == RES_CHRATR_WEIGHT || nWhichId == RES_CHRATR_CJK_WEIGHT;
1565 if (!(bFontSizeWritten && bFontSizeItem) && !(bBoldWritten && bBoldItem))
1566 rAttributeOutput.OutputItem(*pItem);
1567 if (bFontSizeItem)
1568 bFontSizeWritten = true;
1569 if (bBoldItem)
1570 bBoldWritten = true;
1572 else if (nWhichId == RES_TXTATR_AUTOFMT)
1574 const SwFormatAutoFormat pAutoFormat = pItem->StaticWhichCast(RES_TXTATR_AUTOFMT);
1575 lcl_writeParagraphMarkerProperties(rAttributeOutput, *pAutoFormat.GetStyleHandle());
1578 nWhichId = aIter.NextWhich();
1580 rAttributeOutput.GetExport().SetCurItemSet(pOldI);
1583 const char *RubyAlignValues[] =
1585 "center",
1586 "distributeLetter",
1587 "distributeSpace",
1588 "left",
1589 "right",
1590 "rightVertical"
1594 const char *lclConvertWW8JCToOOXMLRubyAlign(sal_Int32 nJC)
1596 const sal_Int32 nElements = SAL_N_ELEMENTS(RubyAlignValues);
1597 if ( nJC >=0 && nJC < nElements )
1598 return RubyAlignValues[nJC];
1599 return RubyAlignValues[0];
1604 void DocxAttributeOutput::EndParagraphProperties(const SfxItemSet& rParagraphMarkerProperties, const SwRedlineData* pRedlineData, const SwRedlineData* pRedlineParagraphMarkerDeleted, const SwRedlineData* pRedlineParagraphMarkerInserted)
1606 // Call the 'Redline' function. This will add redline (change-tracking) information that regards to paragraph properties.
1607 // This includes changes like 'Bold', 'Underline', 'Strikethrough' etc.
1609 // If there is RedlineData present, call WriteCollectedParagraphProperties() for writing pPr before calling Redline().
1610 // As there will be another pPr for redline and LO might mix both.
1611 if(pRedlineData)
1612 WriteCollectedParagraphProperties();
1613 Redline( pRedlineData );
1615 WriteCollectedParagraphProperties();
1617 // Merge the marks for the ordered elements
1618 m_pSerializer->mergeTopMarks(Tag_InitCollectedParagraphProperties);
1620 // Write 'Paragraph Mark' properties
1621 m_pSerializer->startElementNS(XML_w, XML_rPr);
1622 // mark() before paragraph mark properties child elements.
1623 InitCollectedRunProperties();
1625 // The 'm_pFontsAttrList', 'm_pEastAsianLayoutAttrList', 'm_pCharLangAttrList' are used to hold information
1626 // that should be collected by different properties in the core, and are all flushed together
1627 // to the DOCX when the function 'WriteCollectedRunProperties' gets called.
1628 // So we need to store the current status of these lists, so that we can revert back to them when
1629 // we are done exporting the redline attributes.
1630 auto pFontsAttrList_Original(detachFrom(m_pFontsAttrList));
1631 auto pEastAsianLayoutAttrList_Original(detachFrom(m_pEastAsianLayoutAttrList));
1632 auto pCharLangAttrList_Original(detachFrom(m_pCharLangAttrList));
1634 lcl_writeParagraphMarkerProperties(*this, rParagraphMarkerProperties);
1636 // Write the collected run properties that are stored in 'm_pFontsAttrList', 'm_pEastAsianLayoutAttrList', 'm_pCharLangAttrList'
1637 WriteCollectedRunProperties();
1639 // Revert back the original values that were stored in 'm_pFontsAttrList', 'm_pEastAsianLayoutAttrList', 'm_pCharLangAttrList'
1640 m_pFontsAttrList = std::move(pFontsAttrList_Original);
1641 m_pEastAsianLayoutAttrList = std::move(pEastAsianLayoutAttrList_Original);
1642 m_pCharLangAttrList = std::move(pCharLangAttrList_Original);
1644 if ( pRedlineParagraphMarkerDeleted )
1646 StartRedline( pRedlineParagraphMarkerDeleted, /*bLastRun=*/true );
1647 EndRedline( pRedlineParagraphMarkerDeleted, /*bLastRun=*/true );
1649 if ( pRedlineParagraphMarkerInserted )
1651 StartRedline( pRedlineParagraphMarkerInserted, /*bLastRun=*/true );
1652 EndRedline( pRedlineParagraphMarkerInserted, /*bLastRun=*/true );
1655 // mergeTopMarks() after paragraph mark properties child elements.
1656 m_pSerializer->mergeTopMarks(Tag_InitCollectedRunProperties);
1657 m_pSerializer->endElementNS( XML_w, XML_rPr );
1659 if (!m_bWritingHeaderFooter && m_aFramePr.Frame())
1661 const SwFrameFormat& rFrameFormat = m_aFramePr.Frame()->GetFrameFormat();
1662 assert(SwTextBoxHelper::TextBoxIsFramePr(rFrameFormat) && "by definition, because Frame()");
1664 const Size aSize = m_aFramePr.Frame()->GetSize();
1665 PopulateFrameProperties(&rFrameFormat, aSize);
1667 // if the paragraph itself never called FormatBox, do so now
1668 if (m_aFramePr.UseFrameBorders(!m_xTableWrt ? -1 : m_tableReference.m_nTableDepth))
1669 FormatBox(rFrameFormat.GetBox());
1671 if (m_aFramePr.UseFrameBackground())
1673 // The frame is usually imported as 100% transparent. Ignore in that case.
1674 // Background only exports as fully opaque. Emulate - ignore transparency more than 50%
1675 const SwAttrSet& rSet = rFrameFormat.GetAttrSet();
1676 const XFillStyleItem* pFillStyle(rSet.GetItem<XFillStyleItem>(XATTR_FILLSTYLE));
1677 if (pFillStyle && pFillStyle->GetValue() != drawing::FillStyle_NONE)
1679 std::unique_ptr<SvxBrushItem> pBrush(
1680 getSvxBrushItemFromSourceSet(rSet, RES_BACKGROUND));
1681 if (pBrush->GetColor().GetAlpha() > 127) // more opaque than transparent
1683 FormatBackground(*pBrush);
1684 WriteCollectedParagraphProperties();
1689 if (m_aFramePr.UseFrameTextDirection(!m_xTableWrt ? -1 : m_tableReference.m_nTableDepth))
1691 const SvxFrameDirectionItem& rFrameDir = rFrameFormat.GetFrameDir();
1692 if (rFrameDir.GetValue() != SvxFrameDirection::Environment)
1694 assert(!m_rExport.m_bOutPageDescs);
1695 // hack: use existing variable to write out the full TextDirection attribute.
1696 // This is valid for paragraphs/styles - just not native in LO, so hack for now.
1697 m_rExport.m_bOutPageDescs = true;
1698 FormatFrameDirection(rFrameDir);
1699 m_rExport.m_bOutPageDescs = false;
1703 // reset to true in preparation for the next paragraph in the frame
1704 m_aFramePr.SetUseFrameBorders(true);
1705 m_aFramePr.SetUseFrameBackground(true);
1706 m_aFramePr.SetUseFrameTextDirection(true);
1709 m_pSerializer->endElementNS( XML_w, XML_pPr );
1711 // RDF metadata for this text node.
1712 SwTextNode* pTextNode = m_rExport.m_pCurPam->GetPointNode().GetTextNode();
1713 std::map<OUString, OUString> aStatements;
1714 if (pTextNode)
1715 aStatements = SwRDFHelper::getTextNodeStatements(u"urn:bails"_ustr, *pTextNode);
1716 if (!aStatements.empty())
1718 m_pSerializer->startElementNS(XML_w, XML_smartTag,
1719 FSNS(XML_w, XML_uri), "http://www.w3.org/1999/02/22-rdf-syntax-ns#",
1720 FSNS(XML_w, XML_element), "RDF");
1721 m_pSerializer->startElementNS(XML_w, XML_smartTagPr);
1722 for (const auto& rStatement : aStatements)
1723 m_pSerializer->singleElementNS(XML_w, XML_attr,
1724 FSNS(XML_w, XML_name), rStatement.first,
1725 FSNS(XML_w, XML_val), rStatement.second);
1726 m_pSerializer->endElementNS(XML_w, XML_smartTagPr);
1727 m_pSerializer->endElementNS(XML_w, XML_smartTag);
1730 if ((m_nColBreakStatus == COLBRK_WRITE || m_nColBreakStatus == COLBRK_WRITEANDPOSTPONE)
1731 && !m_rExport.SdrExporter().IsDMLAndVMLDrawingOpen())
1733 m_pSerializer->startElementNS(XML_w, XML_r);
1734 m_pSerializer->singleElementNS(XML_w, XML_br, FSNS(XML_w, XML_type), "column");
1735 m_pSerializer->endElementNS( XML_w, XML_r );
1737 if ( m_nColBreakStatus == COLBRK_WRITEANDPOSTPONE )
1738 m_nColBreakStatus = COLBRK_POSTPONE;
1739 else
1740 m_nColBreakStatus = COLBRK_NONE;
1743 if (m_bPostponedPageBreak && !m_bWritingHeaderFooter
1744 && !m_rExport.SdrExporter().IsDMLAndVMLDrawingOpen())
1746 m_pSerializer->startElementNS(XML_w, XML_r);
1747 m_pSerializer->singleElementNS(XML_w, XML_br, FSNS(XML_w, XML_type), "page");
1748 m_pSerializer->endElementNS( XML_w, XML_r );
1750 m_bPostponedPageBreak = false;
1753 // merge the properties _before_ the run (strictly speaking, just
1754 // after the start of the paragraph)
1755 m_pSerializer->mergeTopMarks(Tag_StartParagraphProperties, sax_fastparser::MergeMarks::PREPEND);
1757 m_bOpenedParaPr = false;
1760 void DocxAttributeOutput::SetStateOfFlyFrame( FlyProcessingState nStateOfFlyFrame )
1762 m_nStateOfFlyFrame = nStateOfFlyFrame;
1765 void DocxAttributeOutput::SetAnchorIsLinkedToNode( bool bAnchorLinkedToNode )
1767 m_bAnchorLinkedToNode = bAnchorLinkedToNode ;
1770 void DocxAttributeOutput::ResetFlyProcessingFlag()
1772 m_bPostponedProcessingFly = false ;
1775 bool DocxAttributeOutput::IsFlyProcessingPostponed()
1777 return m_bPostponedProcessingFly;
1780 void DocxAttributeOutput::StartRun( const SwRedlineData* pRedlineData, sal_Int32 /*nPos*/, bool /*bSingleEmptyRun*/ )
1782 // Don't start redline data here, possibly there is a hyperlink later, and
1783 // that has to be started first.
1784 m_pRedlineData = pRedlineData;
1786 // this mark is used to be able to enclose the run inside a sdr tag.
1787 m_pSerializer->mark(Tag_StartRun_1);
1789 // postpone the output of the start of a run (there are elements that need
1790 // to be written before the start of the run, but we learn which they are
1791 // _inside_ of the run)
1792 m_pSerializer->mark(Tag_StartRun_2); // let's call it "postponed run start"
1794 // postpone the output of the text (we get it before the run properties,
1795 // but must write it after them)
1796 m_pSerializer->mark(Tag_StartRun_3); // let's call it "postponed text"
1799 void DocxAttributeOutput::EndRun(const SwTextNode* pNode, sal_Int32 nPos, sal_Int32 nLen, bool bLastRun)
1801 int nFieldsInPrevHyperlink = m_nFieldsInHyperlink;
1802 // Reset m_nFieldsInHyperlink if a new hyperlink is about to start
1803 if ( m_pHyperlinkAttrList.is() )
1805 m_nFieldsInHyperlink = 0;
1808 // Write field starts
1809 for ( std::vector<FieldInfos>::iterator pIt = m_Fields.begin() + nFieldsInPrevHyperlink; pIt != m_Fields.end(); )
1811 // Add the fields starts for all but hyperlinks and TOCs
1812 if (pIt->bOpen && pIt->pField && pIt->eType != ww::eFORMDROPDOWN &&
1813 // it is not an input field with extra grabbag params (sdt field)
1814 (!(pIt->eType == ww::eFILLIN && static_cast<const SwInputField*>(pIt->pField.get())->getGrabBagParams().hasElements()))
1817 StartField_Impl( pNode, nPos, *pIt );
1819 // Remove the field from the stack if only the start has to be written
1820 // Unknown fields should be removed too
1821 if ( !pIt->bClose || ( pIt->eType == ww::eUNKNOWN ) )
1823 pIt = m_Fields.erase( pIt );
1824 continue;
1827 if (m_nHyperLinkCount.back() > 0 || m_pHyperlinkAttrList.is())
1829 ++m_nFieldsInHyperlink;
1832 ++pIt;
1835 // write the run properties + the text, already in the correct order
1836 m_pSerializer->mergeTopMarks(Tag_StartRun_3); // merges with "postponed text", see above
1838 // level down, to be able to prepend the actual run start attribute (just
1839 // before "postponed run start")
1840 m_pSerializer->mark(Tag_EndRun_1); // let's call it "actual run start"
1841 bool bCloseEarlierSDT = false;
1843 if (m_bEndCharSdt)
1845 // This is the common case: "close sdt before the current run" was requested by the next run.
1847 // if another sdt starts in this run, then wait
1848 // as closing the sdt now, might cause nesting of sdts
1849 if (m_aRunSdt.m_nSdtPrToken > 0)
1850 bCloseEarlierSDT = true;
1851 else
1852 m_aRunSdt.EndSdtBlock(m_pSerializer);
1853 m_bEndCharSdt = false;
1856 if ( m_closeHyperlinkInPreviousRun )
1858 if (m_nHyperLinkCount.back() > 0)
1860 for ( int i = 0; i < nFieldsInPrevHyperlink; i++ )
1862 // If fields begin before hyperlink then
1863 // it should end before hyperlink close
1864 EndField_Impl( pNode, nPos, m_Fields.back( ) );
1865 m_Fields.pop_back();
1867 m_pSerializer->endElementNS( XML_w, XML_hyperlink );
1868 m_endPageRef = false;
1869 m_nHyperLinkCount.back()--;
1870 m_closeHyperlinkInPreviousRun = false;
1872 else
1874 bool bIsStartedHyperlink = false;
1875 for (const sal_Int32 nLinkCount : m_nHyperLinkCount)
1877 if (nLinkCount > 0)
1879 bIsStartedHyperlink = true;
1880 break;
1883 if (!bIsStartedHyperlink)
1884 m_closeHyperlinkInPreviousRun = false;
1888 // Write the hyperlink and toc fields starts
1889 for ( std::vector<FieldInfos>::iterator pIt = m_Fields.begin(); pIt != m_Fields.end(); )
1891 // Add the fields starts for hyperlinks, TOCs and index marks
1892 if (pIt->bOpen && (!pIt->pField || pIt->eType == ww::eFORMDROPDOWN ||
1893 // InputField with extra grabbag params - it is sdt field
1894 (pIt->eType == ww::eFILLIN && static_cast<const SwInputField*>(pIt->pField.get())->getGrabBagParams().hasElements())))
1896 StartRedline( m_pRedlineData, bLastRun );
1897 StartField_Impl( pNode, nPos, *pIt, true );
1898 EndRedline( m_pRedlineData, bLastRun );
1900 if (m_nHyperLinkCount.back() > 0)
1901 ++m_nFieldsInHyperlink;
1903 // Remove the field if no end needs to be written
1904 if (!pIt->bSep)
1906 pIt = m_Fields.erase( pIt );
1907 continue;
1910 if (pIt->bSep && !pIt->pField)
1912 // for TOXMark:
1913 // Word ignores bookmarks in field result that is empty;
1914 // work around this by writing bookmark into field command.
1915 if (!m_sFieldBkm.isEmpty())
1917 DoWriteBookmarkTagStart(m_sFieldBkm);
1918 DoWriteBookmarkTagEnd(m_nNextBookmarkId);
1919 m_nNextBookmarkId++;
1920 m_sFieldBkm.clear();
1922 CmdEndField_Impl(pNode, nPos, true);
1923 // Remove the field if no end needs to be written
1924 if (!pIt->bClose)
1926 pIt = m_Fields.erase( pIt );
1927 continue;
1930 ++pIt;
1933 // Start the hyperlink after the fields separators or we would generate invalid file
1934 bool newStartedHyperlink(false);
1935 if ( m_pHyperlinkAttrList.is() )
1937 // if we are ending a hyperlink and there's another one starting here,
1938 // don't do this, so that the fields are closed further down when
1939 // the end hyperlink is handled, which is more likely to put the end in
1940 // the right place, as far as i can tell (not very far in this muck)
1941 if (!m_closeHyperlinkInThisRun)
1943 // end ToX fields that want to end _before_ starting the hyperlink
1944 for (auto it = m_Fields.rbegin(); it != m_Fields.rend(); )
1946 if (it->bClose && !it->pField)
1948 EndField_Impl( pNode, nPos, *it );
1949 it = decltype(m_Fields)::reverse_iterator(m_Fields.erase(it.base() - 1));
1951 else
1953 ++it;
1957 newStartedHyperlink = true;
1959 m_pSerializer->startElementNS( XML_w, XML_hyperlink, detachFrom( m_pHyperlinkAttrList ) );
1960 m_nHyperLinkCount.back()++;
1963 // if there is some redlining in the document, output it
1964 bool bSkipRedline = false;
1965 if (nLen == 1)
1967 // Don't redline content-controls--Word doesn't do them.
1968 SwTextAttr* pAttr
1969 = pNode->GetTextAttrAt(nPos, RES_TXTATR_CONTENTCONTROL, sw::GetTextAttrMode::Default);
1970 if (pAttr && pAttr->GetStart() == nPos)
1972 bSkipRedline = true;
1976 if (!bSkipRedline)
1978 StartRedline(m_pRedlineData, bLastRun);
1981 // XML_r node should be surrounded with bookmark-begin and bookmark-end nodes if it has bookmarks.
1982 // The same is applied for permission ranges.
1983 // But due to unit test "testFdo85542" let's output bookmark-begin with bookmark-end.
1984 DoWriteBookmarksStart(m_rBookmarksStart, m_pMoveRedlineData);
1985 DoWriteBookmarksEnd(m_rBookmarksEnd);
1986 DoWritePermissionsStart();
1987 DoWriteAnnotationMarks();
1989 if (m_closeHyperlinkInThisRun && m_nHyperLinkCount.back() > 0 && !m_hyperLinkAnchor.isEmpty()
1990 && m_hyperLinkAnchor.startsWith("_Toc"))
1992 OUString sToken;
1993 m_pSerializer->startElementNS(XML_w, XML_r);
1994 m_pSerializer->startElementNS(XML_w, XML_rPr);
1995 m_pSerializer->singleElementNS(XML_w, XML_webHidden);
1996 m_pSerializer->endElementNS( XML_w, XML_rPr );
1997 m_pSerializer->startElementNS(XML_w, XML_fldChar, FSNS(XML_w, XML_fldCharType), "begin");
1998 m_pSerializer->endElementNS( XML_w, XML_fldChar );
1999 m_pSerializer->endElementNS( XML_w, XML_r );
2002 m_pSerializer->startElementNS(XML_w, XML_r);
2003 m_pSerializer->startElementNS(XML_w, XML_rPr);
2004 m_pSerializer->singleElementNS(XML_w, XML_webHidden);
2005 m_pSerializer->endElementNS( XML_w, XML_rPr );
2006 sToken = "PAGEREF " + m_hyperLinkAnchor + " \\h"; // '\h' Creates a hyperlink to the bookmarked paragraph.
2007 DoWriteCmd( sToken );
2008 m_pSerializer->endElementNS( XML_w, XML_r );
2010 // Write the Field separator
2011 m_pSerializer->startElementNS(XML_w, XML_r);
2012 m_pSerializer->startElementNS(XML_w, XML_rPr);
2013 m_pSerializer->singleElementNS(XML_w, XML_webHidden);
2014 m_pSerializer->endElementNS( XML_w, XML_rPr );
2015 m_pSerializer->singleElementNS( XML_w, XML_fldChar,
2016 FSNS( XML_w, XML_fldCharType ), "separate" );
2017 m_pSerializer->endElementNS( XML_w, XML_r );
2018 // At start of every "PAGEREF" field m_endPageRef value should be true.
2019 m_endPageRef = true;
2022 DoWriteBookmarkStartIfExist(nPos);
2024 if (nLen != -1)
2026 SwTextAttr* pAttr = pNode->GetTextAttrAt(nPos, RES_TXTATR_CONTENTCONTROL, ::sw::GetTextAttrMode::Default);
2027 if (pAttr && pAttr->GetStart() == nPos)
2029 auto pTextContentControl = static_txtattr_cast<SwTextContentControl*>(pAttr);
2030 m_pContentControl = pTextContentControl->GetContentControl().GetContentControl();
2031 if (!m_tableReference.m_bTableCellChanged)
2033 WriteContentControlStart();
2038 m_pSerializer->startElementNS(XML_w, XML_r);
2039 if(GetExport().m_bTabInTOC && m_pHyperlinkAttrList.is())
2041 RunText(u"\t"_ustr) ;
2043 m_pSerializer->mergeTopMarks(Tag_EndRun_1, sax_fastparser::MergeMarks::PREPEND); // merges with "postponed run start", see above
2045 if ( !m_sRawText.isEmpty() )
2047 RunText( m_sRawText );
2048 m_sRawText.clear();
2051 // write the run start + the run content
2052 m_pSerializer->mergeTopMarks(Tag_StartRun_2); // merges the "actual run start"
2053 // append the actual run end
2054 m_pSerializer->endElementNS( XML_w, XML_r );
2056 // if there is some redlining in the document, output it
2057 // (except in the case of fields with multiple runs)
2058 if (!bSkipRedline)
2060 EndRedline(m_pRedlineData, bLastRun);
2063 if (nLen != -1)
2065 sal_Int32 nEnd = nPos + nLen;
2066 SwTextAttr* pAttr = pNode->GetTextAttrAt(nPos, RES_TXTATR_CONTENTCONTROL, ::sw::GetTextAttrMode::Default);
2067 if (pAttr && *pAttr->GetEnd() == nEnd && !m_tableReference.m_bTableCellChanged)
2069 WriteContentControlEnd();
2073 // enclose in a sdt block, if necessary: if one is already started, then don't do it for now
2074 // (so on export sdt blocks are never nested ATM)
2075 if ( !m_bAnchorLinkedToNode && !m_aRunSdt.m_bStartedSdt)
2077 m_aRunSdt.WriteSdtBlock(m_pSerializer, m_bRunTextIsOn, m_rExport.SdrExporter().IsParagraphHasDrawing());
2079 else
2081 //These should be written out to the actual Node and not to the anchor.
2082 //Clear them as they will be repopulated when the node is processed.
2083 m_aRunSdt.m_nSdtPrToken = 0;
2084 m_aRunSdt.DeleteAndResetTheLists();
2087 if (bCloseEarlierSDT)
2089 m_pSerializer->mark(Tag_EndRun_2);
2090 m_aRunSdt.EndSdtBlock(m_pSerializer);
2091 m_pSerializer->mergeTopMarks(Tag_EndRun_2, sax_fastparser::MergeMarks::PREPEND);
2094 m_pSerializer->mergeTopMarks(Tag_StartRun_1);
2096 // XML_r node should be surrounded with permission-begin and permission-end nodes if it has permission.
2097 DoWritePermissionsEnd();
2099 for (const auto& rpMath : m_aPostponedMaths)
2100 WritePostponedMath(rpMath.pMathObject, rpMath.nMathObjAlignment);
2101 m_aPostponedMaths.clear();
2103 for (const auto& rpControl : m_aPostponedFormControls)
2104 WritePostponedFormControl(rpControl);
2105 m_aPostponedFormControls.clear();
2107 WritePostponedActiveXControl(false);
2109 WritePendingPlaceholder();
2111 if ( !m_bWritingField )
2113 m_pRedlineData = nullptr;
2116 if ( m_closeHyperlinkInThisRun )
2118 if (m_nHyperLinkCount.back() > 0)
2120 if( m_endPageRef )
2122 // Hyperlink is started and fldchar "end" needs to be written for PAGEREF
2123 m_pSerializer->startElementNS(XML_w, XML_r);
2124 m_pSerializer->startElementNS(XML_w, XML_rPr);
2125 m_pSerializer->singleElementNS(XML_w, XML_webHidden);
2126 m_pSerializer->endElementNS( XML_w, XML_rPr );
2127 m_pSerializer->singleElementNS( XML_w, XML_fldChar,
2128 FSNS( XML_w, XML_fldCharType ), "end" );
2129 m_pSerializer->endElementNS( XML_w, XML_r );
2130 m_endPageRef = false;
2131 m_hyperLinkAnchor.clear();
2133 for ( int i = 0; i < m_nFieldsInHyperlink; i++ )
2135 // If fields begin after hyperlink start then
2136 // it should end before hyperlink close
2137 EndField_Impl( pNode, nPos, m_Fields.back( ) );
2138 m_Fields.pop_back();
2140 m_nFieldsInHyperlink = 0;
2142 m_pSerializer->endElementNS( XML_w, XML_hyperlink );
2143 m_nHyperLinkCount.back()--;
2144 m_closeHyperlinkInThisRun = false;
2146 else
2148 bool bIsStartedHyperlink = false;
2149 for (const sal_Int32 nLinkCount : m_nHyperLinkCount)
2151 if (nLinkCount > 0)
2153 bIsStartedHyperlink = true;
2154 break;
2157 if (!bIsStartedHyperlink)
2158 m_closeHyperlinkInThisRun = false;
2162 if (!newStartedHyperlink)
2164 while ( m_Fields.begin() != m_Fields.end() )
2166 EndField_Impl( pNode, nPos, m_Fields.front( ) );
2167 m_Fields.erase( m_Fields.begin( ) );
2169 m_nFieldsInHyperlink = 0;
2172 // end ToX fields
2173 for (auto it = m_Fields.rbegin(); it != m_Fields.rend(); )
2175 if (it->bClose && !it->pField)
2177 EndField_Impl( pNode, nPos, *it );
2178 it = decltype(m_Fields)::reverse_iterator(m_Fields.erase(it.base() - 1));
2180 else
2182 ++it;
2186 if ( m_pRedlineData )
2188 EndRedline( m_pRedlineData, bLastRun );
2189 m_pRedlineData = nullptr;
2192 DoWriteBookmarksStart(m_rFinalBookmarksStart);
2193 DoWriteBookmarksEnd(m_rFinalBookmarksEnd);
2194 DoWriteBookmarkEndIfExist(nPos);
2197 void DocxAttributeOutput::DoWriteBookmarkTagStart(const OUString& bookmarkName)
2199 m_pSerializer->singleElementNS(XML_w, XML_bookmarkStart,
2200 FSNS(XML_w, XML_id), OString::number(m_nNextBookmarkId),
2201 FSNS(XML_w, XML_name), GetExport().BookmarkToWord(bookmarkName));
2204 void DocxAttributeOutput::DoWriteBookmarkTagEnd(sal_Int32 const nId)
2206 m_pSerializer->singleElementNS(XML_w, XML_bookmarkEnd,
2207 FSNS(XML_w, XML_id), OString::number(nId));
2210 void DocxAttributeOutput::DoWriteMoveRangeTagStart(std::u16string_view bookmarkName,
2211 bool bFrom, const SwRedlineData* pRedlineData)
2213 bool bRemovePersonalInfo = SvtSecurityOptions::IsOptionSet(
2214 SvtSecurityOptions::EOption::DocWarnRemovePersonalInfo ) && !SvtSecurityOptions::IsOptionSet(
2215 SvtSecurityOptions::EOption::DocWarnKeepRedlineInfo);
2217 const OUString& rAuthor(SwModule::get()->GetRedlineAuthor(pRedlineData->GetAuthor()));
2218 const DateTime& aDateTime = pRedlineData->GetTimeStamp();
2219 bool bNoDate = bRemovePersonalInfo ||
2220 ( aDateTime.GetYear() == 1970 && aDateTime.GetMonth() == 1 && aDateTime.GetDay() == 1 );
2222 rtl::Reference<sax_fastparser::FastAttributeList> pAttributeList
2223 = sax_fastparser::FastSerializerHelper::createAttrList();
2225 pAttributeList->add(FSNS(XML_w, XML_id), OString::number(m_nNextBookmarkId));
2226 pAttributeList->add(FSNS(XML_w, XML_author ), bRemovePersonalInfo
2227 ? "Author" + OString::number( GetExport().GetInfoID(rAuthor) )
2228 : OUStringToOString(rAuthor, RTL_TEXTENCODING_UTF8));
2229 if (!bNoDate)
2230 pAttributeList->add(FSNS(XML_w, XML_date ), DateTimeToOString( aDateTime ));
2231 pAttributeList->add(FSNS(XML_w, XML_name), bookmarkName);
2232 m_pSerializer->singleElementNS( XML_w, bFrom ? XML_moveFromRangeStart : XML_moveToRangeStart, pAttributeList );
2234 // tdf#150166 avoid of unpaired moveRangeEnd at moved ToC
2235 m_rSavedBookmarksIds.insert(m_nNextBookmarkId);
2238 void DocxAttributeOutput::DoWriteMoveRangeTagEnd(sal_Int32 const nId, bool bFrom)
2240 if ( m_rSavedBookmarksIds.count(nId) )
2242 m_pSerializer->singleElementNS(XML_w, bFrom
2243 ? XML_moveFromRangeEnd
2244 : XML_moveToRangeEnd,
2245 FSNS(XML_w, XML_id), OString::number(nId));
2247 m_rSavedBookmarksIds.erase(nId);
2251 void DocxAttributeOutput::DoWriteBookmarkStartIfExist(sal_Int32 nRunPos)
2253 auto aRange = m_aBookmarksOfParagraphStart.equal_range(nRunPos);
2254 for( auto aIter = aRange.first; aIter != aRange.second; ++aIter)
2256 DoWriteBookmarkTagStart(aIter->second);
2257 m_rOpenedBookmarksIds[aIter->second] = m_nNextBookmarkId;
2258 m_sLastOpenedBookmark = GetExport().BookmarkToWord(aIter->second);
2259 m_nNextBookmarkId++;
2263 void DocxAttributeOutput::DoWriteBookmarkEndIfExist(sal_Int32 nRunPos)
2265 auto aRange = m_aBookmarksOfParagraphEnd.equal_range(nRunPos);
2266 for( auto aIter = aRange.first; aIter != aRange.second; ++aIter)
2268 // Get the id of the bookmark
2269 auto pPos = m_rOpenedBookmarksIds.find(aIter->second);
2270 if (pPos != m_rOpenedBookmarksIds.end())
2272 // Output the bookmark
2273 DoWriteBookmarkTagEnd(pPos->second);
2274 m_rOpenedBookmarksIds.erase(aIter->second);
2279 /// Write the start bookmarks
2280 void DocxAttributeOutput::DoWriteBookmarksStart(std::vector<OUString>& rStarts, const SwRedlineData* pRedlineData)
2282 for (const OUString & bookmarkName : rStarts)
2284 // Output the bookmark (including MoveBookmark of the tracked moving)
2285 bool bMove = false;
2286 bool bFrom = false;
2287 OUString sBookmarkName = GetExport().BookmarkToWord(bookmarkName, &bMove, &bFrom);
2288 if ( bMove )
2290 // TODO: redline data of MoveBookmark is restored from the first redline of the bookmark
2291 // range. But a later deletion within a tracked moving is still imported as plain
2292 // deletion, so check IsMoved() and skip the export of the tracked moving to avoid
2293 // export with bad author or date
2294 if ( pRedlineData && pRedlineData->IsMoved() )
2295 DoWriteMoveRangeTagStart(sBookmarkName, bFrom, pRedlineData);
2297 else
2298 DoWriteBookmarkTagStart(bookmarkName);
2300 m_rOpenedBookmarksIds[bookmarkName] = m_nNextBookmarkId;
2301 m_sLastOpenedBookmark = sBookmarkName;
2302 m_nNextBookmarkId++;
2304 rStarts.clear();
2307 /// export the end bookmarks
2308 void DocxAttributeOutput::DoWriteBookmarksEnd(std::vector<OUString>& rEnds)
2310 for (const OUString & bookmarkName : rEnds)
2312 // Get the id of the bookmark
2313 auto pPos = m_rOpenedBookmarksIds.find(bookmarkName);
2315 if (pPos != m_rOpenedBookmarksIds.end())
2317 bool bMove = false;
2318 bool bFrom = false;
2319 GetExport().BookmarkToWord(bookmarkName, &bMove, &bFrom);
2320 // Output the bookmark (including MoveBookmark of the tracked moving)
2321 if ( bMove )
2322 DoWriteMoveRangeTagEnd(pPos->second, bFrom);
2323 else
2324 DoWriteBookmarkTagEnd(pPos->second);
2326 m_rOpenedBookmarksIds.erase(bookmarkName);
2329 rEnds.clear();
2332 // For construction of the special bookmark name template for permissions:
2333 // see, PermInsertPosition::createBookmarkName()
2335 // Syntax:
2336 // - "permission-for-user:<permission-id>:<permission-user-name>"
2337 // - "permission-for-group:<permission-id>:<permission-group-name>"
2339 void DocxAttributeOutput::DoWritePermissionTagStart(std::u16string_view permission)
2341 std::u16string_view permissionIdAndName;
2343 if (o3tl::starts_with(permission, u"permission-for-group:", &permissionIdAndName))
2345 const std::size_t separatorIndex = permissionIdAndName.find(u':');
2346 assert(separatorIndex != std::u16string_view::npos);
2347 const OUString permissionId(permissionIdAndName.substr(0, separatorIndex));
2348 const OUString permissionName(permissionIdAndName.substr(separatorIndex + 1));
2350 m_pSerializer->singleElementNS(XML_w, XML_permStart,
2351 FSNS(XML_w, XML_id), GetExport().BookmarkToWord(permissionId),
2352 FSNS(XML_w, XML_edGrp), GetExport().BookmarkToWord(permissionName));
2354 else
2356 auto const ok = o3tl::starts_with(
2357 permission, u"permission-for-user:", &permissionIdAndName);
2358 assert(ok); (void)ok;
2359 const std::size_t separatorIndex = permissionIdAndName.find(u':');
2360 assert(separatorIndex != std::u16string_view::npos);
2361 const OUString permissionId(permissionIdAndName.substr(0, separatorIndex));
2362 const OUString permissionName(permissionIdAndName.substr(separatorIndex + 1));
2364 m_pSerializer->singleElementNS(XML_w, XML_permStart,
2365 FSNS(XML_w, XML_id), GetExport().BookmarkToWord(permissionId),
2366 FSNS(XML_w, XML_ed), GetExport().BookmarkToWord(permissionName));
2371 // For construction of the special bookmark name template for permissions:
2372 // see, PermInsertPosition::createBookmarkName()
2374 // Syntax:
2375 // - "permission-for-user:<permission-id>:<permission-user-name>"
2376 // - "permission-for-group:<permission-id>:<permission-group-name>"
2378 void DocxAttributeOutput::DoWritePermissionTagEnd(std::u16string_view permission)
2380 std::u16string_view permissionIdAndName;
2382 auto const ok = o3tl::starts_with(permission, u"permission-for-group:", &permissionIdAndName) ||
2383 o3tl::starts_with(permission, u"permission-for-user:", &permissionIdAndName);
2384 assert(ok); (void)ok;
2386 const std::size_t separatorIndex = permissionIdAndName.find(u':');
2387 assert(separatorIndex != std::u16string_view::npos);
2388 const OUString permissionId(permissionIdAndName.substr(0, separatorIndex));
2390 m_pSerializer->singleElementNS(XML_w, XML_permEnd,
2391 FSNS(XML_w, XML_id), GetExport().BookmarkToWord(permissionId));
2394 /// Write the start permissions
2395 void DocxAttributeOutput::DoWritePermissionsStart()
2397 for (const OUString & permission : m_rPermissionsStart)
2399 DoWritePermissionTagStart(permission);
2401 m_rPermissionsStart.clear();
2404 /// export the end permissions
2405 void DocxAttributeOutput::DoWritePermissionsEnd()
2407 for (const OUString & permission : m_rPermissionsEnd)
2409 DoWritePermissionTagEnd(permission);
2411 m_rPermissionsEnd.clear();
2414 void DocxAttributeOutput::DoWriteAnnotationMarks()
2416 // Write the start annotation marks
2417 for ( const auto & rName : m_rAnnotationMarksStart )
2419 // Output the annotation mark
2420 /* Ensure that the existing Annotation Marks are not overwritten
2421 as it causes discrepancy when DocxAttributeOutput::PostitField
2422 refers to this map & while mapping comment id's in document.xml &
2423 comment.xml.
2425 if ( m_rOpenedAnnotationMarksIds.end() == m_rOpenedAnnotationMarksIds.find( rName ) )
2427 const sal_Int32 nId = m_nNextAnnotationMarkId++;
2428 m_rOpenedAnnotationMarksIds[rName] = nId;
2429 m_pSerializer->singleElementNS( XML_w, XML_commentRangeStart,
2430 FSNS( XML_w, XML_id ), OString::number(nId) );
2431 m_sLastOpenedAnnotationMark = rName;
2434 m_rAnnotationMarksStart.clear();
2436 // export the end annotation marks
2437 for ( const auto & rName : m_rAnnotationMarksEnd )
2439 // Get the id of the annotation mark
2440 auto pPos = m_rOpenedAnnotationMarksIds.find( rName );
2441 if ( pPos != m_rOpenedAnnotationMarksIds.end( ) )
2443 const sal_Int32 nId = ( *pPos ).second;
2444 m_pSerializer->singleElementNS( XML_w, XML_commentRangeEnd,
2445 FSNS( XML_w, XML_id ), OString::number(nId) );
2446 m_rOpenedAnnotationMarksIds.erase( rName );
2448 m_pSerializer->startElementNS(XML_w, XML_r);
2449 m_pSerializer->singleElementNS( XML_w, XML_commentReference, FSNS( XML_w, XML_id ),
2450 OString::number(nId) );
2451 m_pSerializer->endElementNS(XML_w, XML_r);
2454 m_rAnnotationMarksEnd.clear();
2457 void DocxAttributeOutput::WriteFFData( const FieldInfos& rInfos )
2459 const ::sw::mark::Fieldmark& rFieldmark = *rInfos.pFieldmark;
2460 FieldMarkParamsHelper params( rFieldmark );
2462 OUString sEntryMacro;
2463 params.extractParam(u"EntryMacro"_ustr, sEntryMacro);
2464 OUString sExitMacro;
2465 params.extractParam(u"ExitMacro"_ustr, sExitMacro);
2466 OUString sHelp;
2467 params.extractParam(u"Help"_ustr, sHelp);
2468 OUString sHint;
2469 params.extractParam(u"Hint"_ustr, sHint); // .docx StatusText
2470 if ( sHint.isEmpty() )
2471 params.extractParam(u"Description"_ustr, sHint); // .doc StatusText
2473 if ( rInfos.eType == ww::eFORMDROPDOWN )
2475 uno::Sequence< OUString> vListEntries;
2476 OUString sName, sSelected;
2478 params.extractParam( ODF_FORMDROPDOWN_LISTENTRY, vListEntries );
2479 if (vListEntries.getLength() > ODF_FORMDROPDOWN_ENTRY_COUNT_LIMIT)
2480 vListEntries = uno::Sequence< OUString>(vListEntries.getArray(), ODF_FORMDROPDOWN_ENTRY_COUNT_LIMIT);
2482 sName = params.getName();
2483 sal_Int32 nSelectedIndex = 0;
2485 if ( params.extractParam( ODF_FORMDROPDOWN_RESULT, nSelectedIndex ) )
2487 if (nSelectedIndex < vListEntries.getLength() )
2488 sSelected = vListEntries[ nSelectedIndex ];
2491 GetExport().DoComboBox( sName, OUString(), OUString(), sSelected, vListEntries );
2493 else if ( rInfos.eType == ww::eFORMCHECKBOX )
2495 const OUString& sName = params.getName();
2496 bool bChecked = false;
2498 const sw::mark::CheckboxFieldmark* pCheckboxFm = dynamic_cast<const sw::mark::CheckboxFieldmark*>(&rFieldmark);
2499 if ( pCheckboxFm && pCheckboxFm->IsChecked() )
2500 bChecked = true;
2502 FFDataWriterHelper ffdataOut( m_pSerializer );
2503 ffdataOut.WriteFormCheckbox( sName, sEntryMacro, sExitMacro, sHelp, sHint, bChecked );
2505 else if ( rInfos.eType == ww::eFORMTEXT )
2507 OUString sType;
2508 params.extractParam(u"Type"_ustr, sType);
2509 OUString sDefaultText;
2510 params.extractParam(u"Content"_ustr, sDefaultText);
2511 sal_uInt16 nMaxLength = 0;
2512 params.extractParam(u"MaxLength"_ustr, nMaxLength);
2513 OUString sFormat;
2514 params.extractParam(u"Format"_ustr, sFormat);
2515 FFDataWriterHelper ffdataOut( m_pSerializer );
2516 ffdataOut.WriteFormText( params.getName(), sEntryMacro, sExitMacro, sHelp, sHint,
2517 sType, sDefaultText, nMaxLength, sFormat );
2521 void DocxAttributeOutput::WriteFormDateStart(const OUString& sFullDate, const OUString& sDateFormat, const OUString& sLang, const uno::Sequence<beans::PropertyValue>& aGrabBagSdt)
2523 m_pSerializer->startElementNS(XML_w, XML_sdt);
2524 m_pSerializer->startElementNS(XML_w, XML_sdtPr);
2526 if(!sFullDate.isEmpty())
2527 m_pSerializer->startElementNS(XML_w, XML_date, FSNS(XML_w, XML_fullDate), sFullDate);
2528 else
2529 m_pSerializer->startElementNS(XML_w, XML_date);
2531 // Replace quotation mark used for marking static strings in date format
2532 OUString sDateFormat1 = sDateFormat.replaceAll("\"", "'");
2533 m_pSerializer->singleElementNS(XML_w, XML_dateFormat,
2534 FSNS(XML_w, XML_val), sDateFormat1);
2535 m_pSerializer->singleElementNS(XML_w, XML_lid,
2536 FSNS(XML_w, XML_val), sLang);
2537 m_pSerializer->singleElementNS(XML_w, XML_storeMappedDataAs,
2538 FSNS(XML_w, XML_val), "dateTime");
2539 m_pSerializer->singleElementNS(XML_w, XML_calendar,
2540 FSNS(XML_w, XML_val), "gregorian");
2541 m_pSerializer->endElementNS(XML_w, XML_date);
2543 if (aGrabBagSdt.hasElements())
2545 // There are some extra sdt parameters came from grab bag
2546 SdtBlockHelper aSdtBlock;
2547 aSdtBlock.GetSdtParamsFromGrabBag(aGrabBagSdt);
2548 aSdtBlock.WriteExtraParams(m_pSerializer);
2551 m_pSerializer->endElementNS(XML_w, XML_sdtPr);
2553 m_pSerializer->startElementNS(XML_w, XML_sdtContent);
2556 void DocxAttributeOutput::WriteSdtPlainText(const OUString & sValue, const uno::Sequence<beans::PropertyValue>& aGrabBagSdt)
2558 m_pSerializer->startElementNS(XML_w, XML_sdt);
2559 m_pSerializer->startElementNS(XML_w, XML_sdtPr);
2561 if (aGrabBagSdt.hasElements())
2563 // There are some extra sdt parameters came from grab bag
2564 SdtBlockHelper aSdtBlock;
2565 aSdtBlock.GetSdtParamsFromGrabBag(aGrabBagSdt);
2566 aSdtBlock.WriteExtraParams(m_pSerializer);
2568 if (aSdtBlock.m_nSdtPrToken && aSdtBlock.m_nSdtPrToken != FSNS(XML_w, XML_id))
2570 // Write <w:text/> or whatsoever from grabbag
2571 m_pSerializer->singleElement(aSdtBlock.m_nSdtPrToken);
2574 // Store databindings data for later writing to corresponding XMLs
2575 OUString sPrefixMapping, sXpath;
2576 for (const auto& rProp : aGrabBagSdt)
2578 if (rProp.Name == "ooxml:CT_SdtPr_dataBinding")
2580 uno::Sequence<beans::PropertyValue> aDataBindingProps;
2581 rProp.Value >>= aDataBindingProps;
2582 for (const auto& rDBProp : aDataBindingProps)
2584 if (rDBProp.Name == "ooxml:CT_DataBinding_prefixMappings")
2585 sPrefixMapping = rDBProp.Value.get<OUString>();
2586 else if (rDBProp.Name == "ooxml:CT_DataBinding_xpath")
2587 sXpath = rDBProp.Value.get<OUString>();
2592 if (sXpath.getLength())
2594 // Given xpath is sufficient
2595 m_rExport.AddSdtData(sPrefixMapping, sXpath, sValue);
2599 m_pSerializer->endElementNS(XML_w, XML_sdtPr);
2601 m_pSerializer->startElementNS(XML_w, XML_sdtContent);
2604 void DocxAttributeOutput::WriteContentControlStart()
2606 if (!m_pContentControl)
2608 return;
2611 m_pSerializer->startElementNS(XML_w, XML_sdt);
2612 m_pSerializer->startElementNS(XML_w, XML_sdtPr);
2613 if (!m_pContentControl->GetPlaceholderDocPart().isEmpty())
2615 m_pSerializer->startElementNS(XML_w, XML_placeholder);
2616 m_pSerializer->singleElementNS(XML_w, XML_docPart, FSNS(XML_w, XML_val),
2617 m_pContentControl->GetPlaceholderDocPart());
2618 m_pSerializer->endElementNS(XML_w, XML_placeholder);
2621 if (!m_pContentControl->GetDataBindingPrefixMappings().isEmpty() || !m_pContentControl->GetDataBindingXpath().isEmpty() || !m_pContentControl->GetDataBindingStoreItemID().isEmpty())
2623 m_pSerializer->singleElementNS( XML_w, XML_dataBinding,
2624 FSNS(XML_w, XML_prefixMappings), m_pContentControl->GetDataBindingPrefixMappings(),
2625 FSNS(XML_w, XML_xpath), m_pContentControl->GetDataBindingXpath(),
2626 FSNS(XML_w, XML_storeItemID), m_pContentControl->GetDataBindingStoreItemID());
2629 if (!m_pContentControl->GetColor().isEmpty())
2631 m_pSerializer->singleElementNS(XML_w15, XML_color, FSNS(XML_w, XML_val),
2632 m_pContentControl->GetColor());
2635 if (!m_pContentControl->GetAppearance().isEmpty())
2637 m_pSerializer->singleElementNS(XML_w15, XML_appearance, FSNS(XML_w15, XML_val),
2638 m_pContentControl->GetAppearance());
2641 if (!m_pContentControl->GetAlias().isEmpty())
2643 m_pSerializer->singleElementNS(XML_w, XML_alias, FSNS(XML_w, XML_val),
2644 m_pContentControl->GetAlias());
2647 if (!m_pContentControl->GetTag().isEmpty())
2649 m_pSerializer->singleElementNS(XML_w, XML_tag, FSNS(XML_w, XML_val),
2650 m_pContentControl->GetTag());
2653 if (m_pContentControl->GetId())
2655 m_pSerializer->singleElementNS(XML_w, XML_id, FSNS(XML_w, XML_val),
2656 OString::number(m_pContentControl->GetId()));
2659 if (m_pContentControl->GetTabIndex())
2661 // write the unsigned value as if it were signed since that is all we can import
2662 const sal_Int32 nTabIndex = static_cast<sal_Int32>(m_pContentControl->GetTabIndex());
2663 m_pSerializer->singleElementNS(XML_w, XML_tabIndex, FSNS(XML_w, XML_val),
2664 OString::number(nTabIndex));
2667 if (!m_pContentControl->GetLock().isEmpty())
2669 m_pSerializer->singleElementNS(XML_w, XML_lock, FSNS(XML_w, XML_val),
2670 m_pContentControl->GetLock());
2673 if (m_pContentControl->GetShowingPlaceHolder())
2675 m_pSerializer->singleElementNS(XML_w, XML_showingPlcHdr);
2678 if (m_pContentControl->GetPicture())
2680 m_pSerializer->singleElementNS(XML_w, XML_picture);
2683 if (m_pContentControl->GetCheckbox())
2685 m_pSerializer->startElementNS(XML_w14, XML_checkbox);
2686 m_pSerializer->singleElementNS(XML_w14, XML_checked, FSNS(XML_w14, XML_val),
2687 OString::number(int(m_pContentControl->GetChecked())));
2688 OUString aCheckedState = m_pContentControl->GetCheckedState();
2689 if (!aCheckedState.isEmpty())
2691 m_pSerializer->singleElementNS(XML_w14, XML_checkedState, FSNS(XML_w14, XML_val),
2692 OString::number(aCheckedState[0], /*radix=*/16));
2694 OUString aUncheckedState = m_pContentControl->GetUncheckedState();
2695 if (!aUncheckedState.isEmpty())
2697 m_pSerializer->singleElementNS(XML_w14, XML_uncheckedState, FSNS(XML_w14, XML_val),
2698 OString::number(aUncheckedState[0], /*radix=*/16));
2700 m_pSerializer->endElementNS(XML_w14, XML_checkbox);
2703 if (m_pContentControl->GetComboBox() || m_pContentControl->GetDropDown())
2705 if (m_pContentControl->GetComboBox())
2707 m_pSerializer->startElementNS(XML_w, XML_comboBox);
2709 else
2711 m_pSerializer->startElementNS(XML_w, XML_dropDownList);
2713 for (const auto& rItem : m_pContentControl->GetListItems())
2715 if (rItem.m_aDisplayText.isEmpty() && rItem.m_aValue.isEmpty())
2717 // Empty display text & value would be invalid DOCX, skip the item.
2718 continue;
2721 rtl::Reference<FastAttributeList> xAttributes = FastSerializerHelper::createAttrList();
2722 if (!rItem.m_aDisplayText.isEmpty())
2724 // If there is no display text, need to omit the attribute, not write an empty one.
2725 xAttributes->add(FSNS(XML_w, XML_displayText), rItem.m_aDisplayText);
2728 OUString aValue = rItem.m_aValue;
2729 if (aValue.isEmpty())
2731 // Empty value would be invalid DOCX, default to the display text.
2732 aValue = rItem.m_aDisplayText;
2734 xAttributes->add(FSNS(XML_w, XML_value), rItem.m_aValue);
2735 m_pSerializer->singleElementNS(XML_w, XML_listItem, xAttributes);
2737 if (m_pContentControl->GetComboBox())
2739 m_pSerializer->endElementNS(XML_w, XML_comboBox);
2741 else
2743 m_pSerializer->endElementNS(XML_w, XML_dropDownList);
2747 if (m_pContentControl->GetDate())
2749 OUString aCurrentDate = m_pContentControl->GetCurrentDate();
2750 if (aCurrentDate.isEmpty())
2752 m_pSerializer->startElementNS(XML_w, XML_date);
2754 else
2756 m_pSerializer->startElementNS(XML_w, XML_date, FSNS(XML_w, XML_fullDate), aCurrentDate);
2758 OUString aDateFormat = m_pContentControl->GetDateFormat().replaceAll("\"", "'");
2759 if (!aDateFormat.isEmpty())
2761 m_pSerializer->singleElementNS(XML_w, XML_dateFormat, FSNS(XML_w, XML_val),
2762 aDateFormat);
2764 OUString aDateLanguage = m_pContentControl->GetDateLanguage();
2765 if (!aDateLanguage.isEmpty())
2767 m_pSerializer->singleElementNS(XML_w, XML_lid, FSNS(XML_w, XML_val),
2768 aDateLanguage);
2770 m_pSerializer->endElementNS(XML_w, XML_date);
2773 if (!m_pContentControl->GetMultiLine().isEmpty())
2775 m_pSerializer->singleElementNS(XML_w, XML_text, FSNS(XML_w, XML_multiLine), m_pContentControl->GetMultiLine());
2777 else if (m_pContentControl->GetPlainText())
2779 m_pSerializer->singleElementNS(XML_w, XML_text);
2782 m_pSerializer->endElementNS(XML_w, XML_sdtPr);
2783 m_pSerializer->startElementNS(XML_w, XML_sdtContent);
2785 const OUString& rPrefixMapping = m_pContentControl->GetDataBindingPrefixMappings();
2786 const OUString& rXpath = m_pContentControl->GetDataBindingXpath();
2787 if (!rXpath.isEmpty())
2789 // This content control has a data binding, update the data source.
2790 SwTextContentControl* pTextAttr = m_pContentControl->GetTextAttr();
2791 SwTextNode* pTextNode = m_pContentControl->GetTextNode();
2792 if (pTextNode && pTextAttr)
2794 SwPosition aPoint(*pTextNode, pTextAttr->GetStart());
2795 SwPosition aMark(*pTextNode, *pTextAttr->GetEnd());
2796 SwPaM aPam(aMark, aPoint);
2797 OUString aSnippet = aPam.GetText();
2798 static sal_Unicode const aForbidden[] = {
2799 CH_TXTATR_BREAKWORD,
2802 aSnippet = comphelper::string::removeAny(aSnippet, aForbidden);
2803 m_rExport.AddSdtData(rPrefixMapping, rXpath, aSnippet);
2807 m_pContentControl = nullptr;
2810 void DocxAttributeOutput::WriteContentControlEnd()
2812 m_pSerializer->endElementNS(XML_w, XML_sdtContent);
2813 m_pSerializer->endElementNS(XML_w, XML_sdt);
2816 void DocxAttributeOutput::WriteSdtDropDownStart(
2817 OUString const& rName,
2818 OUString const& rSelected,
2819 uno::Sequence<OUString> const& rListItems)
2821 m_pSerializer->startElementNS(XML_w, XML_sdt);
2822 m_pSerializer->startElementNS(XML_w, XML_sdtPr);
2824 m_pSerializer->singleElementNS(XML_w, XML_alias,
2825 FSNS(XML_w, XML_val), rName);
2827 sal_Int32 nId = comphelper::findValue(rListItems, rSelected);
2828 if (nId == -1)
2830 nId = 0;
2833 m_pSerializer->startElementNS(XML_w, XML_dropDownList,
2834 FSNS(XML_w, XML_lastValue), OString::number(nId));
2836 for (auto const& rItem : rListItems)
2838 auto const item(OUStringToOString(rItem, RTL_TEXTENCODING_UTF8));
2839 m_pSerializer->singleElementNS(XML_w, XML_listItem,
2840 FSNS(XML_w, XML_value), item,
2841 FSNS(XML_w, XML_displayText), item);
2844 m_pSerializer->endElementNS(XML_w, XML_dropDownList);
2845 m_pSerializer->endElementNS(XML_w, XML_sdtPr);
2847 m_pSerializer->startElementNS(XML_w, XML_sdtContent);
2850 void DocxAttributeOutput::WriteSdtDropDownEnd(OUString const& rSelected,
2851 uno::Sequence<OUString> const& rListItems)
2853 // note: rSelected might be empty?
2854 sal_Int32 nId = comphelper::findValue(rListItems, rSelected);
2855 if (nId == -1)
2857 nId = 0;
2860 // the lastValue only identifies the entry in the list, also export
2861 // currently selected item's displayText as run content (if one exists)
2862 if (rListItems.size())
2864 m_pSerializer->startElementNS(XML_w, XML_r);
2865 m_pSerializer->startElementNS(XML_w, XML_t);
2866 m_pSerializer->writeEscaped(rListItems[nId]);
2867 m_pSerializer->endElementNS(XML_w, XML_t);
2868 m_pSerializer->endElementNS(XML_w, XML_r);
2871 WriteContentControlEnd();
2874 void DocxAttributeOutput::StartField_Impl( const SwTextNode* pNode, sal_Int32 nPos, FieldInfos const & rInfos, bool bWriteRun )
2876 if ( rInfos.pField && rInfos.eType == ww::eUNKNOWN )
2878 // Expand unsupported fields
2879 RunText(rInfos.pField->ExpandField(/*bCached=*/true, nullptr));
2880 return;
2882 else if ( rInfos.eType == ww::eFORMDATE )
2884 const sw::mark::DateFieldmark& rFieldmark = dynamic_cast<const sw::mark::DateFieldmark&>(*rInfos.pFieldmark);
2885 FieldMarkParamsHelper params(rFieldmark);
2887 OUString sFullDate;
2888 OUString sCurrentDate;
2889 params.extractParam( ODF_FORMDATE_CURRENTDATE, sCurrentDate );
2890 if(!sCurrentDate.isEmpty())
2892 sFullDate = sCurrentDate + "T00:00:00Z";
2894 else
2896 std::pair<bool, double> aResult = rFieldmark.GetCurrentDate();
2897 if(aResult.first)
2899 sFullDate = rFieldmark.GetDateInStandardDateFormat(aResult.second) + "T00:00:00Z";
2903 OUString sDateFormat;
2904 params.extractParam( ODF_FORMDATE_DATEFORMAT, sDateFormat );
2905 OUString sLang;
2906 params.extractParam( ODF_FORMDATE_DATEFORMAT_LANGUAGE, sLang );
2908 uno::Sequence<beans::PropertyValue> aSdtParams;
2909 params.extractParam(UNO_NAME_MISC_OBJ_INTEROPGRABBAG, aSdtParams);
2911 WriteFormDateStart( sFullDate, sDateFormat, sLang, aSdtParams);
2912 return;
2914 else if (rInfos.eType == ww::eFORMDROPDOWN && rInfos.pField)
2916 assert(!rInfos.pFieldmark);
2917 SwDropDownField const& rField2(*static_cast<SwDropDownField const*>(rInfos.pField.get()));
2918 WriteSdtDropDownStart(rField2.GetName(),
2919 rField2.GetSelectedItem(),
2920 rField2.GetItemSequence());
2921 return;
2923 else if (rInfos.eType == ww::eFILLIN)
2925 const SwInputField* pField = static_cast<SwInputField const*>(rInfos.pField.get());
2926 if (pField && pField->getGrabBagParams().hasElements())
2928 WriteSdtPlainText(pField->GetPar1(), pField->getGrabBagParams());
2929 m_sRawText = pField->GetPar1(); // Write field content also as a fallback
2930 return;
2934 if ( rInfos.eType != ww::eNONE ) // HYPERLINK fields are just commands
2936 if ( bWriteRun )
2937 m_pSerializer->startElementNS(XML_w, XML_r);
2939 if ( rInfos.eType == ww::eFORMDROPDOWN )
2941 m_pSerializer->startElementNS( XML_w, XML_fldChar,
2942 FSNS( XML_w, XML_fldCharType ), "begin" );
2943 assert( rInfos.pFieldmark && !rInfos.pField );
2944 WriteFFData(rInfos);
2945 m_pSerializer->endElementNS( XML_w, XML_fldChar );
2947 if ( bWriteRun )
2948 m_pSerializer->endElementNS( XML_w, XML_r );
2950 CmdField_Impl( pNode, nPos, rInfos, bWriteRun );
2952 else
2954 // Write the field start
2955 if ( rInfos.pField && (rInfos.pField->Which() == SwFieldIds::DateTime) && rInfos.pField->GetSubType() & FIXEDFLD )
2957 m_pSerializer->startElementNS( XML_w, XML_fldChar,
2958 FSNS( XML_w, XML_fldCharType ), "begin",
2959 FSNS( XML_w, XML_fldLock ), "true" );
2961 else
2963 m_pSerializer->startElementNS( XML_w, XML_fldChar,
2964 FSNS( XML_w, XML_fldCharType ), "begin" );
2967 if ( rInfos.pFieldmark )
2968 WriteFFData( rInfos );
2970 m_pSerializer->endElementNS( XML_w, XML_fldChar );
2972 if ( bWriteRun )
2973 m_pSerializer->endElementNS( XML_w, XML_r );
2975 // The hyperlinks fields can't be expanded: the value is
2976 // normally in the text run
2977 if ( !rInfos.pField )
2978 CmdField_Impl( pNode, nPos, rInfos, bWriteRun );
2979 else
2980 m_bWritingField = true;
2985 void DocxAttributeOutput::DoWriteCmd( std::u16string_view rCmd )
2987 std::u16string_view sCmd = o3tl::trim(rCmd);
2988 if (o3tl::starts_with(sCmd, u"SEQ"))
2990 OUString sSeqName( o3tl::trim(msfilter::util::findQuotedText(sCmd, u"SEQ ", '\\')) );
2991 m_aSeqBookmarksNames[sSeqName].push_back(m_sLastOpenedBookmark);
2993 // Write the Field command
2994 sal_Int32 nTextToken = XML_instrText;
2995 if ( m_pRedlineData && m_pRedlineData->GetType() == RedlineType::Delete )
2996 nTextToken = XML_delInstrText;
2998 m_pSerializer->startElementNS(XML_w, nTextToken, FSNS(XML_xml, XML_space), "preserve");
2999 m_pSerializer->writeEscaped( rCmd );
3000 m_pSerializer->endElementNS( XML_w, nTextToken );
3004 void DocxAttributeOutput::CmdField_Impl( const SwTextNode* pNode, sal_Int32 nPos, FieldInfos const & rInfos, bool bWriteRun )
3006 // Write the Field instruction
3007 if ( bWriteRun )
3009 bool bWriteCombChars(false);
3010 m_pSerializer->startElementNS(XML_w, XML_r);
3012 if (rInfos.eType == ww::eEQ)
3013 bWriteCombChars = true;
3015 DoWriteFieldRunProperties( pNode, nPos, bWriteCombChars );
3018 sal_Int32 nIdx { rInfos.sCmd.isEmpty() ? -1 : 0 };
3019 while ( nIdx >= 0 )
3021 OUString sToken = rInfos.sCmd.getToken( 0, '\t', nIdx );
3022 if ( rInfos.eType == ww::eCREATEDATE
3023 || rInfos.eType == ww::eSAVEDATE
3024 || rInfos.eType == ww::ePRINTDATE
3025 || rInfos.eType == ww::eDATE
3026 || rInfos.eType == ww::eTIME )
3028 sToken = sToken.replaceAll("NNNN", "dddd");
3029 sToken = sToken.replaceAll("NN", "ddd");
3031 else if ( rInfos.eType == ww::eEquals )
3033 // Use original OOXML formula, if it exists and its conversion hasn't been changed
3034 bool bIsChanged = true;
3035 if ( pNode->GetTableBox() )
3037 if ( const SfxGrabBagItem* pItem = pNode->GetTableBox()->GetFrameFormat()->GetAttrSet().GetItem<SfxGrabBagItem>(RES_FRMATR_GRABBAG) )
3039 OUString sActualFormula = sToken.trim();
3040 const std::map<OUString, uno::Any>& rGrabBag = pItem->GetGrabBag();
3041 std::map<OUString, uno::Any>::const_iterator aStoredFormula = rGrabBag.find(u"CellFormulaConverted"_ustr);
3042 if ( aStoredFormula != rGrabBag.end() && sActualFormula.indexOf('=') == 0 &&
3043 o3tl::trim(sActualFormula.subView(1)) == o3tl::trim(aStoredFormula->second.get<OUString>()) )
3045 aStoredFormula = rGrabBag.find(u"CellFormula"_ustr);
3046 if ( aStoredFormula != rGrabBag.end() )
3048 sToken = " =" + aStoredFormula->second.get<OUString>();
3049 bIsChanged = false;
3055 if ( bIsChanged )
3057 UErrorCode nErr(U_ZERO_ERROR);
3058 icu::UnicodeString sInput(sToken.getStr());
3059 // remove < and > around cell references, e.g. <A1> to A1, <A1:B2> to A1:B2
3060 icu::RegexMatcher aMatcher("<([A-Z]{1,3}[0-9]+(:[A-Z]{1,3}[0-9]+)?)>", sInput, 0, nErr);
3061 sInput = aMatcher.replaceAll(icu::UnicodeString("$1"), nErr);
3062 // convert MEAN to AVERAGE
3063 icu::RegexMatcher aMatcher2("\\bMEAN\\b", sInput, UREGEX_CASE_INSENSITIVE, nErr);
3064 sToken = aMatcher2.replaceAll(icu::UnicodeString("AVERAGE"), nErr).getTerminatedBuffer();
3068 // Write the Field command
3069 DoWriteCmd( sToken );
3071 // Replace tabs by </instrText><tab/><instrText>
3072 if ( nIdx > 0 ) // Is another token expected?
3073 RunText( u"\t"_ustr );
3076 if ( bWriteRun )
3078 m_pSerializer->endElementNS( XML_w, XML_r );
3082 void DocxAttributeOutput::CmdEndField_Impl(SwTextNode const*const pNode,
3083 sal_Int32 const nPos, bool const bWriteRun)
3085 // Write the Field separator
3086 if ( bWriteRun )
3088 m_pSerializer->startElementNS(XML_w, XML_r);
3089 DoWriteFieldRunProperties( pNode, nPos );
3092 m_pSerializer->singleElementNS( XML_w, XML_fldChar,
3093 FSNS( XML_w, XML_fldCharType ), "separate" );
3095 if ( bWriteRun )
3097 m_pSerializer->endElementNS( XML_w, XML_r );
3101 /// Writes properties for run that is used to separate field implementation.
3102 /// There are several runs are used:
3103 /// <w:r>
3104 /// <w:rPr>
3105 /// <!-- properties written with StartRunProperties() / EndRunProperties().
3106 /// </w:rPr>
3107 /// <w:fldChar w:fldCharType="begin" />
3108 /// </w:r>
3109 /// <w:r>
3110 /// <w:rPr>
3111 /// <!-- properties written with DoWriteFieldRunProperties()
3112 /// </w:rPr>
3113 /// <w:instrText>TIME \@"HH:mm:ss"</w:instrText>
3114 /// </w:r>
3115 /// <w:r>
3116 /// <w:rPr>
3117 /// <!-- properties written with DoWriteFieldRunProperties()
3118 /// </w:rPr>
3119 /// <w:fldChar w:fldCharType="separate" />
3120 /// </w:r>
3121 /// <w:r>
3122 /// <w:rPr>
3123 /// <!-- properties written with DoWriteFieldRunProperties()
3124 /// </w:rPr>
3125 /// <w:t>14:01:13</w:t>
3126 /// </w:r>
3127 /// <w:r>
3128 /// <w:rPr>
3129 /// <!-- properties written with DoWriteFieldRunProperties()
3130 /// </w:rPr>
3131 /// <w:fldChar w:fldCharType="end" />
3132 /// </w:r>
3133 /// See, tdf#38778
3134 void DocxAttributeOutput::DoWriteFieldRunProperties( const SwTextNode * pNode, sal_Int32 nPos, bool bWriteCombChars)
3136 if (! pNode)
3138 // nothing to do
3139 return;
3142 m_bPreventDoubleFieldsHandling = true;
3145 m_pSerializer->startElementNS(XML_w, XML_rPr);
3147 // 1. output webHidden flag
3148 if(GetExport().m_bHideTabLeaderAndPageNumbers && m_pHyperlinkAttrList.is() )
3150 m_pSerializer->singleElementNS(XML_w, XML_webHidden);
3153 // 2. find all active character properties
3154 SwWW8AttrIter aAttrIt( m_rExport, *pNode );
3155 aAttrIt.OutAttr( nPos, bWriteCombChars );
3157 // 3. write the character properties
3158 WriteCollectedRunProperties();
3160 m_pSerializer->endElementNS( XML_w, XML_rPr );
3163 m_bPreventDoubleFieldsHandling = false;
3166 void DocxAttributeOutput::EndField_Impl( const SwTextNode* pNode, sal_Int32 nPos, FieldInfos& rInfos )
3168 if (rInfos.eType == ww::eFORMDATE)
3170 WriteContentControlEnd();
3171 return;
3173 else if (rInfos.eType == ww::eFORMDROPDOWN && rInfos.pField)
3175 // write selected item from End not Start to ensure that any bookmarks
3176 // precede it
3177 SwDropDownField const& rField(*static_cast<SwDropDownField const*>(rInfos.pField.get()));
3178 WriteSdtDropDownEnd(rField.GetSelectedItem(), rField.GetItemSequence());
3179 return;
3181 else if (rInfos.eType == ww::eFILLIN && rInfos.pField)
3183 SwInputField const& rField(*static_cast<SwInputField const*>(rInfos.pField.get()));
3184 if (rField.getGrabBagParams().hasElements())
3186 WriteContentControlEnd();
3187 return;
3190 // The command has to be written before for the hyperlinks
3191 if ( rInfos.pField )
3193 CmdField_Impl( pNode, nPos, rInfos, true );
3194 CmdEndField_Impl( pNode, nPos, true );
3197 // Write the bookmark start if any
3198 if ( !m_sFieldBkm.isEmpty() )
3200 DoWriteBookmarkTagStart(m_sFieldBkm);
3203 if (rInfos.pField ) // For hyperlinks and TOX
3205 // Write the Field latest value
3206 m_pSerializer->startElementNS(XML_w, XML_r);
3207 DoWriteFieldRunProperties( pNode, nPos );
3209 OUString sExpand;
3210 if(rInfos.eType == ww::eCITATION)
3212 sExpand = static_cast<SwAuthorityField const*>(rInfos.pField.get())
3213 ->ExpandCitation(AUTH_FIELD_TITLE, nullptr);
3215 else if(rInfos.eType != ww::eFORMDROPDOWN)
3217 sExpand = rInfos.pField->ExpandField(true, nullptr);
3219 // newlines embedded in fields are 0x0B in MSO and 0x0A for us
3220 RunText(sExpand.replace(0x0A, 0x0B));
3222 m_pSerializer->endElementNS( XML_w, XML_r );
3225 // Write the bookmark end if any
3226 if ( !m_sFieldBkm.isEmpty() )
3228 DoWriteBookmarkTagEnd(m_nNextBookmarkId);
3230 m_nNextBookmarkId++;
3233 // Write the Field end
3234 if ( rInfos.bClose )
3236 m_bWritingField = false;
3237 m_pSerializer->startElementNS(XML_w, XML_r);
3238 DoWriteFieldRunProperties( pNode, nPos );
3239 m_pSerializer->singleElementNS(XML_w, XML_fldChar, FSNS(XML_w, XML_fldCharType), "end");
3240 m_pSerializer->endElementNS( XML_w, XML_r );
3242 // Write the ref field if a bookmark had to be set and the field
3243 // should be visible
3244 if ( !rInfos.pField )
3246 m_sFieldBkm.clear();
3247 return;
3250 sal_uInt16 nSubType = rInfos.pField->GetSubType( );
3251 bool bIsSetField = rInfos.pField->GetTyp( )->Which( ) == SwFieldIds::SetExp;
3252 bool bShowRef = bIsSetField && ( nSubType & nsSwExtendedSubType::SUB_INVISIBLE ) == 0;
3254 if (!bShowRef)
3256 m_sFieldBkm.clear();
3259 if (m_sFieldBkm.isEmpty())
3260 return;
3262 // Write the field beginning
3263 m_pSerializer->startElementNS(XML_w, XML_r);
3264 m_pSerializer->singleElementNS( XML_w, XML_fldChar,
3265 FSNS( XML_w, XML_fldCharType ), "begin" );
3266 m_pSerializer->endElementNS( XML_w, XML_r );
3268 rInfos.sCmd = FieldString( ww::eREF );
3269 rInfos.sCmd += "\"";
3270 rInfos.sCmd += m_sFieldBkm;
3271 rInfos.sCmd += "\" ";
3273 // Clean the field bookmark data to avoid infinite loop
3274 m_sFieldBkm = OUString( );
3276 // Write the end of the field
3277 EndField_Impl( pNode, nPos, rInfos );
3280 void DocxAttributeOutput::StartRunProperties()
3282 // postpone the output so that we can later [in EndRunProperties()]
3283 // prepend the properties before the text
3284 m_pSerializer->mark(Tag_StartRunProperties);
3286 m_pSerializer->startElementNS(XML_w, XML_rPr);
3288 if(GetExport().m_bHideTabLeaderAndPageNumbers && m_pHyperlinkAttrList.is() )
3290 m_pSerializer->singleElementNS(XML_w, XML_webHidden);
3292 InitCollectedRunProperties();
3294 assert( !m_oPostponedGraphic );
3295 m_oPostponedGraphic.emplace();
3297 assert( !m_oPostponedDiagrams );
3298 m_oPostponedDiagrams.emplace();
3300 assert(!m_oPostponedDMLDrawings);
3301 m_oPostponedDMLDrawings.emplace();
3303 assert( !m_oPostponedOLEs );
3304 m_oPostponedOLEs.emplace();
3307 void DocxAttributeOutput::InitCollectedRunProperties()
3309 m_pFontsAttrList = nullptr;
3310 m_pEastAsianLayoutAttrList = nullptr;
3311 m_pCharLangAttrList = nullptr;
3313 // Write the elements in the spec order
3314 static const sal_Int32 aOrder[] =
3316 FSNS( XML_w, XML_rStyle ),
3317 FSNS( XML_w, XML_rFonts ),
3318 FSNS( XML_w, XML_b ),
3319 FSNS( XML_w, XML_bCs ),
3320 FSNS( XML_w, XML_i ),
3321 FSNS( XML_w, XML_iCs ),
3322 FSNS( XML_w, XML_caps ),
3323 FSNS( XML_w, XML_smallCaps ),
3324 FSNS( XML_w, XML_strike ),
3325 FSNS( XML_w, XML_dstrike ),
3326 FSNS( XML_w, XML_outline ),
3327 FSNS( XML_w, XML_shadow ),
3328 FSNS( XML_w, XML_emboss ),
3329 FSNS( XML_w, XML_imprint ),
3330 FSNS( XML_w, XML_noProof ),
3331 FSNS( XML_w, XML_snapToGrid ),
3332 FSNS( XML_w, XML_vanish ),
3333 FSNS( XML_w, XML_webHidden ),
3334 FSNS( XML_w, XML_color ),
3335 FSNS( XML_w, XML_spacing ),
3336 FSNS( XML_w, XML_w ),
3337 FSNS( XML_w, XML_kern ),
3338 FSNS( XML_w, XML_position ),
3339 FSNS( XML_w, XML_sz ),
3340 FSNS( XML_w, XML_szCs ),
3341 FSNS( XML_w, XML_highlight ),
3342 FSNS( XML_w, XML_u ),
3343 FSNS( XML_w, XML_effect ),
3344 FSNS( XML_w, XML_bdr ),
3345 FSNS( XML_w, XML_shd ),
3346 FSNS( XML_w, XML_fitText ),
3347 FSNS( XML_w, XML_vertAlign ),
3348 FSNS( XML_w, XML_rtl ),
3349 FSNS( XML_w, XML_cs ),
3350 FSNS( XML_w, XML_em ),
3351 FSNS( XML_w, XML_lang ),
3352 FSNS( XML_w, XML_eastAsianLayout ),
3353 FSNS( XML_w, XML_specVanish ),
3354 FSNS( XML_w, XML_oMath ),
3355 FSNS( XML_w, XML_rPrChange ),
3356 FSNS( XML_w, XML_del ),
3357 FSNS( XML_w, XML_ins ),
3358 FSNS( XML_w, XML_moveFrom ),
3359 FSNS( XML_w, XML_moveTo ),
3360 FSNS( XML_w14, XML_glow ),
3361 FSNS( XML_w14, XML_shadow ),
3362 FSNS( XML_w14, XML_reflection ),
3363 FSNS( XML_w14, XML_textOutline ),
3364 FSNS( XML_w14, XML_textFill ),
3365 FSNS( XML_w14, XML_scene3d ),
3366 FSNS( XML_w14, XML_props3d ),
3367 FSNS( XML_w14, XML_ligatures ),
3368 FSNS( XML_w14, XML_numForm ),
3369 FSNS( XML_w14, XML_numSpacing ),
3370 FSNS( XML_w14, XML_stylisticSets ),
3371 FSNS( XML_w14, XML_cntxtAlts ),
3374 // postpone the output so that we can later [in EndParagraphProperties()]
3375 // prepend the properties before the run
3376 // coverity[overrun-buffer-arg : FALSE] - coverity has difficulty with css::uno::Sequence
3377 m_pSerializer->mark(Tag_InitCollectedRunProperties, comphelper::containerToSequence(aOrder));
3380 namespace
3383 struct NameToId
3385 OUString maName;
3386 sal_Int32 maId;
3389 const NameToId constNameToIdMapping[] =
3391 { u"glow"_ustr, FSNS( XML_w14, XML_glow ) },
3392 { u"shadow"_ustr, FSNS( XML_w14, XML_shadow ) },
3393 { u"reflection"_ustr, FSNS( XML_w14, XML_reflection ) },
3394 { u"textOutline"_ustr, FSNS( XML_w14, XML_textOutline ) },
3395 { u"textFill"_ustr, FSNS( XML_w14, XML_textFill ) },
3396 { u"scene3d"_ustr, FSNS( XML_w14, XML_scene3d ) },
3397 { u"props3d"_ustr, FSNS( XML_w14, XML_props3d ) },
3398 { u"ligatures"_ustr, FSNS( XML_w14, XML_ligatures ) },
3399 { u"numForm"_ustr, FSNS( XML_w14, XML_numForm ) },
3400 { u"numSpacing"_ustr, FSNS( XML_w14, XML_numSpacing ) },
3401 { u"stylisticSets"_ustr,FSNS( XML_w14, XML_stylisticSets ) },
3402 { u"cntxtAlts"_ustr, FSNS( XML_w14, XML_cntxtAlts ) },
3404 { u"val"_ustr, FSNS( XML_w14, XML_val ) },
3405 { u"rad"_ustr, FSNS( XML_w14, XML_rad ) },
3406 { u"blurRad"_ustr, FSNS( XML_w14, XML_blurRad ) },
3407 { u"stA"_ustr, FSNS( XML_w14, XML_stA ) },
3408 { u"stPos"_ustr, FSNS( XML_w14, XML_stPos ) },
3409 { u"endA"_ustr, FSNS( XML_w14, XML_endA ) },
3410 { u"endPos"_ustr, FSNS( XML_w14, XML_endPos ) },
3411 { u"dist"_ustr, FSNS( XML_w14, XML_dist ) },
3412 { u"dir"_ustr, FSNS( XML_w14, XML_dir ) },
3413 { u"fadeDir"_ustr, FSNS( XML_w14, XML_fadeDir ) },
3414 { u"sx"_ustr, FSNS( XML_w14, XML_sx ) },
3415 { u"sy"_ustr, FSNS( XML_w14, XML_sy ) },
3416 { u"kx"_ustr, FSNS( XML_w14, XML_kx ) },
3417 { u"ky"_ustr, FSNS( XML_w14, XML_ky ) },
3418 { u"algn"_ustr, FSNS( XML_w14, XML_algn ) },
3419 { u"w"_ustr, FSNS( XML_w14, XML_w ) },
3420 { u"cap"_ustr, FSNS( XML_w14, XML_cap ) },
3421 { u"cmpd"_ustr, FSNS( XML_w14, XML_cmpd ) },
3422 { u"pos"_ustr, FSNS( XML_w14, XML_pos ) },
3423 { u"ang"_ustr, FSNS( XML_w14, XML_ang ) },
3424 { u"scaled"_ustr, FSNS( XML_w14, XML_scaled ) },
3425 { u"path"_ustr, FSNS( XML_w14, XML_path ) },
3426 { u"l"_ustr, FSNS( XML_w14, XML_l ) },
3427 { u"t"_ustr, FSNS( XML_w14, XML_t ) },
3428 { u"r"_ustr, FSNS( XML_w14, XML_r ) },
3429 { u"b"_ustr, FSNS( XML_w14, XML_b ) },
3430 { u"lim"_ustr, FSNS( XML_w14, XML_lim ) },
3431 { u"prst"_ustr, FSNS( XML_w14, XML_prst ) },
3432 { u"rig"_ustr, FSNS( XML_w14, XML_rig ) },
3433 { u"lat"_ustr, FSNS( XML_w14, XML_lat ) },
3434 { u"lon"_ustr, FSNS( XML_w14, XML_lon ) },
3435 { u"rev"_ustr, FSNS( XML_w14, XML_rev ) },
3436 { u"h"_ustr, FSNS( XML_w14, XML_h ) },
3437 { u"extrusionH"_ustr, FSNS( XML_w14, XML_extrusionH ) },
3438 { u"contourW"_ustr, FSNS( XML_w14, XML_contourW ) },
3439 { u"prstMaterial"_ustr, FSNS( XML_w14, XML_prstMaterial ) },
3440 { u"id"_ustr, FSNS( XML_w14, XML_id ) },
3442 { u"schemeClr"_ustr, FSNS( XML_w14, XML_schemeClr ) },
3443 { u"srgbClr"_ustr, FSNS( XML_w14, XML_srgbClr ) },
3444 { u"tint"_ustr, FSNS( XML_w14, XML_tint ) },
3445 { u"shade"_ustr, FSNS( XML_w14, XML_shade ) },
3446 { u"alpha"_ustr, FSNS( XML_w14, XML_alpha ) },
3447 { u"hueMod"_ustr, FSNS( XML_w14, XML_hueMod ) },
3448 { u"sat"_ustr, FSNS( XML_w14, XML_sat ) },
3449 { u"satOff"_ustr, FSNS( XML_w14, XML_satOff ) },
3450 { u"satMod"_ustr, FSNS( XML_w14, XML_satMod ) },
3451 { u"lum"_ustr, FSNS( XML_w14, XML_lum ) },
3452 { u"lumOff"_ustr, FSNS( XML_w14, XML_lumOff ) },
3453 { u"lumMod"_ustr, FSNS( XML_w14, XML_lumMod ) },
3454 { u"noFill"_ustr, FSNS( XML_w14, XML_noFill ) },
3455 { u"solidFill"_ustr, FSNS( XML_w14, XML_solidFill ) },
3456 { u"gradFill"_ustr, FSNS( XML_w14, XML_gradFill ) },
3457 { u"gsLst"_ustr, FSNS( XML_w14, XML_gsLst ) },
3458 { u"gs"_ustr, FSNS( XML_w14, XML_gs ) },
3459 { u"pos"_ustr, FSNS( XML_w14, XML_pos ) },
3460 { u"lin"_ustr, FSNS( XML_w14, XML_lin ) },
3461 { u"path"_ustr, FSNS( XML_w14, XML_path ) },
3462 { u"fillToRect"_ustr, FSNS( XML_w14, XML_fillToRect ) },
3463 { u"prstDash"_ustr, FSNS( XML_w14, XML_prstDash ) },
3464 { u"round"_ustr, FSNS( XML_w14, XML_round ) },
3465 { u"bevel"_ustr, FSNS( XML_w14, XML_bevel ) },
3466 { u"miter"_ustr, FSNS( XML_w14, XML_miter ) },
3467 { u"camera"_ustr, FSNS( XML_w14, XML_camera ) },
3468 { u"lightRig"_ustr, FSNS( XML_w14, XML_lightRig ) },
3469 { u"rot"_ustr, FSNS( XML_w14, XML_rot ) },
3470 { u"bevelT"_ustr, FSNS( XML_w14, XML_bevelT ) },
3471 { u"bevelB"_ustr, FSNS( XML_w14, XML_bevelB ) },
3472 { u"extrusionClr"_ustr, FSNS( XML_w14, XML_extrusionClr ) },
3473 { u"contourClr"_ustr, FSNS( XML_w14, XML_contourClr ) },
3474 { u"styleSet"_ustr, FSNS( XML_w14, XML_styleSet ) },
3477 std::optional<sal_Int32> lclGetElementIdForName(std::u16string_view rName)
3479 for (auto const & i : constNameToIdMapping)
3481 if (rName == i.maName)
3483 return i.maId;
3486 return std::optional<sal_Int32>();
3489 void lclProcessRecursiveGrabBag(sal_Int32 aElementId, const css::uno::Sequence<css::beans::PropertyValue>& rElements, sax_fastparser::FSHelperPtr const & pSerializer)
3491 css::uno::Sequence<css::beans::PropertyValue> aAttributes;
3492 rtl::Reference<FastAttributeList> pAttributes = FastSerializerHelper::createAttrList();
3493 sal_Int32 nElements = 0;
3495 for (const auto& rElement : rElements)
3497 if (rElement.Name == "attributes")
3499 rElement.Value >>= aAttributes;
3501 else
3503 ++nElements;
3507 for (const auto& rAttribute : aAttributes)
3509 uno::Any aAny = rAttribute.Value;
3510 OString aValue;
3512 if(aAny.getValueType() == cppu::UnoType<sal_Int32>::get())
3514 aValue = OString::number(aAny.get<sal_Int32>());
3516 else if(aAny.getValueType() == cppu::UnoType<OUString>::get())
3518 aValue = OUStringToOString(aAny.get<OUString>(), RTL_TEXTENCODING_ASCII_US);
3521 std::optional<sal_Int32> aSubElementId = lclGetElementIdForName(rAttribute.Name);
3522 if(aSubElementId)
3523 pAttributes->add(*aSubElementId, aValue);
3526 if (nElements == 0)
3528 pSerializer->singleElement(aElementId, pAttributes);
3530 else
3532 pSerializer->startElement(aElementId, pAttributes);
3534 for (const auto& rElement : rElements)
3536 css::uno::Sequence<css::beans::PropertyValue> aSumElements;
3538 std::optional<sal_Int32> aSubElementId = lclGetElementIdForName(rElement.Name);
3539 if(aSubElementId)
3541 rElement.Value >>= aSumElements;
3542 lclProcessRecursiveGrabBag(*aSubElementId, aSumElements, pSerializer);
3546 pSerializer->endElement(aElementId);
3550 constexpr auto constTransformationToTokenId = frozen::make_unordered_map<model::TransformationType, sal_Int32>({
3551 { model::TransformationType::Tint, XML_tint },
3552 { model::TransformationType::Shade, XML_shade },
3553 { model::TransformationType::Sat, XML_sat },
3554 { model::TransformationType::SatOff, XML_satOff },
3555 { model::TransformationType::SatMod, XML_satMod },
3556 { model::TransformationType::Lum, XML_lum },
3557 { model::TransformationType::LumOff, XML_lumOff },
3558 { model::TransformationType::LumMod, XML_lumMod },
3561 } // end anonymous namespace
3563 void DocxAttributeOutput::WriteCollectedRunProperties()
3565 // Write all differed properties
3566 if ( m_pFontsAttrList.is() )
3568 m_pSerializer->singleElementNS( XML_w, XML_rFonts, detachFrom( m_pFontsAttrList ) );
3571 if ( m_pColorAttrList.is() )
3573 m_pSerializer->singleElementNS( XML_w, XML_color, m_pColorAttrList );
3576 if ( m_pEastAsianLayoutAttrList.is() )
3578 m_pSerializer->singleElementNS( XML_w, XML_eastAsianLayout,
3579 detachFrom(m_pEastAsianLayoutAttrList ) );
3582 if ( m_pCharLangAttrList.is() )
3584 m_pSerializer->singleElementNS( XML_w, XML_lang, detachFrom( m_pCharLangAttrList ) );
3587 if ((m_nCharTransparence != 0 || lclHasSolidFillTransformations(m_aComplexColor))
3588 && m_pColorAttrList.is()
3589 && m_aTextFillGrabBag.empty())
3591 std::string_view pVal;
3592 m_pColorAttrList->getAsView(FSNS(XML_w, XML_val), pVal);
3594 if (!pVal.empty() && pVal != "auto")
3596 m_pSerializer->startElementNS(XML_w14, XML_textFill);
3597 m_pSerializer->startElementNS(XML_w14, XML_solidFill);
3599 if (m_aComplexColor.isValidThemeType())
3601 OString sSchemeType = lclGetSchemeType(m_aComplexColor);
3602 m_pSerializer->startElementNS(XML_w14, XML_schemeClr, FSNS(XML_w14, XML_val), sSchemeType);
3604 else
3606 m_pSerializer->startElementNS(XML_w14, XML_srgbClr, FSNS(XML_w14, XML_val), pVal.data());
3609 if (m_nCharTransparence != 0)
3611 sal_Int32 nTransparence = basegfx::fround(m_nCharTransparence / 255.0 * 100.0) * oox::drawingml::PER_PERCENT;
3612 m_pSerializer->singleElementNS(XML_w14, XML_alpha, FSNS(XML_w14, XML_val), OString::number(nTransparence));
3615 for (const model::Transformation & transformation : m_aComplexColor.getTransformations())
3617 sal_Int32 nValue = transformation.mnValue * drawingml::PER_PERCENT;
3618 const auto iter = constTransformationToTokenId.find(transformation.meType);
3619 if (iter != constTransformationToTokenId.end())
3621 sal_Int32 nElement = iter->second;
3622 m_pSerializer->singleElementNS(XML_w14, nElement, FSNS(XML_w14, XML_val), OString::number(nValue));
3626 if (m_aComplexColor.isValidThemeType())
3628 m_pSerializer->endElementNS(XML_w14, XML_schemeClr);
3630 else
3632 m_pSerializer->endElementNS(XML_w14, XML_srgbClr);
3634 m_pSerializer->endElementNS(XML_w14, XML_solidFill);
3635 m_pSerializer->endElementNS(XML_w14, XML_textFill);
3636 m_nCharTransparence = 0;
3639 m_pColorAttrList.clear();
3641 auto processGrabBag = [this](const beans::PropertyValue& prop)
3643 std::optional<sal_Int32> aElementId = lclGetElementIdForName(prop.Name);
3644 if(aElementId)
3646 uno::Sequence<beans::PropertyValue> aGrabBagSeq;
3647 prop.Value >>= aGrabBagSeq;
3648 lclProcessRecursiveGrabBag(*aElementId, aGrabBagSeq, m_pSerializer);
3652 for (const beans::PropertyValue & i : m_aTextEffectsGrabBag)
3654 processGrabBag(i);
3656 for (const beans::PropertyValue & i : m_aTextFillGrabBag)
3658 processGrabBag(i);
3660 m_aTextEffectsGrabBag.clear();
3661 m_aTextFillGrabBag.clear();
3662 // export vanish and specVanish for the newly created inline headings
3663 if ( m_bOpenedParaPr && m_rExport.m_bParaInlineHeading )
3665 m_pSerializer->singleElementNS(XML_w, XML_vanish);
3666 m_pSerializer->singleElementNS(XML_w, XML_specVanish);
3667 m_rExport.m_bParaInlineHeading = false;
3671 void DocxAttributeOutput::EndRunProperties( const SwRedlineData* pRedlineData )
3673 // Call the 'Redline' function. This will add redline (change-tracking) information that regards to run properties.
3674 // This includes changes like 'Bold', 'Underline', 'Strikethrough' etc.
3676 // If there is RedlineData present, call WriteCollectedRunProperties() for writing rPr before calling Redline().
3677 // As there will be another rPr for redline and LO might mix both.
3678 if(pRedlineData)
3679 WriteCollectedRunProperties();
3680 Redline( pRedlineData );
3682 WriteCollectedRunProperties();
3684 // Merge the marks for the ordered elements
3685 m_pSerializer->mergeTopMarks(Tag_InitCollectedRunProperties);
3687 m_pSerializer->endElementNS( XML_w, XML_rPr );
3689 // write footnotes/endnotes if we have any
3690 FootnoteEndnoteReference();
3692 WriteLineBreak();
3694 // merge the properties _before_ the run text (strictly speaking, just
3695 // after the start of the run)
3696 m_pSerializer->mergeTopMarks(Tag_StartRunProperties, sax_fastparser::MergeMarks::PREPEND);
3698 WritePostponedGraphic();
3700 WritePostponedDiagram();
3701 //We need to write w:drawing tag after the w:rPr.
3702 WritePostponedChart();
3704 //We need to write w:pict tag after the w:rPr.
3705 WritePostponedDMLDrawing();
3707 WritePostponedOLE();
3709 WritePostponedActiveXControl(true);
3712 void DocxAttributeOutput::GetSdtEndBefore(const SdrObject* pSdrObj)
3714 if (!pSdrObj)
3715 return;
3717 uno::Reference<drawing::XShape> xShape(const_cast<SdrObject*>(pSdrObj)->getUnoShape());
3718 uno::Reference< beans::XPropertySet > xPropSet( xShape, uno::UNO_QUERY );
3719 if( !xPropSet.is() )
3720 return;
3722 uno::Reference< beans::XPropertySetInfo > xPropSetInfo = xPropSet->getPropertySetInfo();
3723 uno::Sequence< beans::PropertyValue > aGrabBag;
3724 if (xPropSetInfo.is() && xPropSetInfo->hasPropertyByName(u"FrameInteropGrabBag"_ustr))
3726 xPropSet->getPropertyValue(u"FrameInteropGrabBag"_ustr) >>= aGrabBag;
3728 else if(xPropSetInfo.is() && xPropSetInfo->hasPropertyByName(u"InteropGrabBag"_ustr))
3730 xPropSet->getPropertyValue(u"InteropGrabBag"_ustr) >>= aGrabBag;
3733 auto pProp = std::find_if(std::cbegin(aGrabBag), std::cend(aGrabBag),
3734 [this](const beans::PropertyValue& rProp) {
3735 return "SdtEndBefore" == rProp.Name && m_aRunSdt.m_bStartedSdt && !m_bEndCharSdt; });
3736 if (pProp != std::cend(aGrabBag))
3737 pProp->Value >>= m_bEndCharSdt;
3740 void DocxAttributeOutput::WritePostponedGraphic()
3742 for (const auto & rPostponedDiagram : *m_oPostponedGraphic)
3743 FlyFrameGraphic(rPostponedDiagram.grfNode, rPostponedDiagram.size,
3744 nullptr, nullptr,
3745 rPostponedDiagram.pSdrObj);
3746 m_oPostponedGraphic.reset();
3749 void DocxAttributeOutput::WritePostponedDiagram()
3751 for( const auto & rPostponedDiagram : *m_oPostponedDiagrams )
3752 m_rExport.SdrExporter().writeDiagram(rPostponedDiagram.object,
3753 *rPostponedDiagram.frame, m_anchorId++);
3754 m_oPostponedDiagrams.reset();
3757 bool DocxAttributeOutput::FootnoteEndnoteRefTag()
3759 if( m_footnoteEndnoteRefTag == 0 )
3760 return false;
3762 // output the character style for MS Word's benefit
3763 const SwEndNoteInfo& rInfo = m_footnoteEndnoteRefTag == XML_footnoteRef ?
3764 m_rExport.m_rDoc.GetFootnoteInfo() : m_rExport.m_rDoc.GetEndNoteInfo();
3765 const SwCharFormat* pCharFormat = rInfo.GetCharFormat( m_rExport.m_rDoc );
3766 if ( pCharFormat )
3768 const OString aStyleId(m_rExport.m_pStyles->GetStyleId(m_rExport.GetId(pCharFormat)));
3769 m_pSerializer->startElementNS(XML_w, XML_rPr);
3770 m_pSerializer->singleElementNS(XML_w, XML_rStyle, FSNS(XML_w, XML_val), aStyleId);
3771 m_pSerializer->endElementNS( XML_w, XML_rPr );
3774 if (m_footnoteCustomLabel.isEmpty())
3775 m_pSerializer->singleElementNS(XML_w, m_footnoteEndnoteRefTag);
3776 else
3777 RunText(m_footnoteCustomLabel);
3778 m_footnoteEndnoteRefTag = 0;
3779 return true;
3782 /** Output sal_Unicode* as a run text (<t>the text</t>).
3784 When bMove is true, update rBegin to point _after_ the end of the text +
3785 1, meaning that it skips one character after the text. This is to make
3786 the switch in DocxAttributeOutput::RunText() nicer ;-)
3788 static bool impl_WriteRunText( FSHelperPtr const & pSerializer, sal_Int32 nTextToken,
3789 const sal_Unicode* &rBegin, const sal_Unicode* pEnd, bool bMove = true,
3790 const OUString& rSymbolFont = OUString() )
3792 const sal_Unicode *pBegin = rBegin;
3794 // skip one character after the end
3795 if ( bMove )
3796 rBegin = pEnd + 1;
3798 if ( pBegin >= pEnd )
3799 return false; // we want to write at least one character
3801 bool bIsSymbol = !rSymbolFont.isEmpty();
3803 std::u16string_view aView( pBegin, pEnd - pBegin );
3804 if (bIsSymbol)
3806 for (char16_t aChar : aView)
3808 pSerializer->singleElementNS(XML_w, XML_sym,
3809 FSNS(XML_w, XML_font), rSymbolFont,
3810 FSNS(XML_w, XML_char), OString::number(aChar, 16));
3813 else
3815 // we have to add 'preserve' when starting/ending with space
3816 if ( *pBegin == ' ' || *( pEnd - 1 ) == ' ' )
3817 pSerializer->startElementNS(XML_w, nTextToken, FSNS(XML_xml, XML_space), "preserve");
3818 else
3819 pSerializer->startElementNS(XML_w, nTextToken);
3821 pSerializer->writeEscaped( aView );
3822 pSerializer->endElementNS( XML_w, nTextToken );
3825 return true;
3828 void DocxAttributeOutput::RunText( const OUString& rText, rtl_TextEncoding /*eCharSet*/, const OUString& rSymbolFont )
3830 if( m_closeHyperlinkInThisRun )
3832 m_closeHyperlinkInPreviousRun = true;
3834 m_bRunTextIsOn = true;
3835 // one text can be split into more <w:t>blah</w:t>'s by line breaks etc.
3836 const sal_Unicode *pBegin = rText.getStr();
3837 const sal_Unicode *pEnd = pBegin + rText.getLength();
3839 // the text run is usually XML_t, with the exception of the deleted (and not moved) text
3840 sal_Int32 nTextToken = XML_t;
3842 bool bMoved = m_pRedlineData && m_pRedlineData->IsMoved() &&
3843 // tdf#150166 save tracked moving around TOC as w:ins, w:del
3844 SwDoc::GetCurTOX(*m_rExport.m_pCurPam->GetPoint()) == nullptr;
3846 if ( m_pRedlineData && m_pRedlineData->GetType() == RedlineType::Delete && !bMoved )
3848 nTextToken = XML_delText;
3851 sal_Unicode prevUnicode = *pBegin;
3853 for ( const sal_Unicode *pIt = pBegin; pIt < pEnd; ++pIt )
3855 switch ( *pIt )
3857 case 0x09: // tab
3858 impl_WriteRunText( m_pSerializer, nTextToken, pBegin, pIt );
3859 m_pSerializer->singleElementNS(XML_w, XML_tab);
3860 prevUnicode = *pIt;
3861 break;
3862 case 0x0b: // line break
3863 case static_cast<sal_Unicode>(text::ControlCharacter::LINE_BREAK):
3865 if (impl_WriteRunText( m_pSerializer, nTextToken, pBegin, pIt ) || prevUnicode < 0x0020)
3867 m_pSerializer->singleElementNS(XML_w, XML_br);
3868 prevUnicode = *pIt;
3871 break;
3872 case 0x1E: //non-breaking hyphen
3873 impl_WriteRunText( m_pSerializer, nTextToken, pBegin, pIt );
3874 m_pSerializer->singleElementNS(XML_w, XML_noBreakHyphen);
3875 prevUnicode = *pIt;
3876 break;
3877 case 0x1F: //soft (on demand) hyphen
3878 impl_WriteRunText( m_pSerializer, nTextToken, pBegin, pIt );
3879 m_pSerializer->singleElementNS(XML_w, XML_softHyphen);
3880 prevUnicode = *pIt;
3881 break;
3882 default:
3883 if ( *pIt < 0x0020 ) // filter out the control codes
3885 impl_WriteRunText( m_pSerializer, nTextToken, pBegin, pIt );
3886 SAL_INFO("sw.ww8", "Ignored control code in a text run: " << unsigned(*pIt) );
3888 prevUnicode = *pIt;
3889 break;
3893 impl_WriteRunText( m_pSerializer, nTextToken, pBegin, pEnd, false, rSymbolFont );
3896 void DocxAttributeOutput::RawText(const OUString& rText, rtl_TextEncoding /*eCharSet*/)
3898 m_sRawText = rText;
3901 void DocxAttributeOutput::StartRuby( const SwTextNode& rNode, sal_Int32 nPos, const SwFormatRuby& rRuby )
3903 WW8Ruby aWW8Ruby( rNode, rRuby, GetExport() );
3904 SAL_INFO("sw.ww8", "TODO DocxAttributeOutput::StartRuby( const SwTextNode& rNode, const SwFormatRuby& rRuby )" );
3905 EndRun( &rNode, nPos, -1 ); // end run before starting ruby to avoid nested runs, and overlap
3906 assert(!m_closeHyperlinkInThisRun); // check that no hyperlink overlaps ruby
3907 assert(!m_closeHyperlinkInPreviousRun);
3908 m_pSerializer->startElementNS(XML_w, XML_r);
3909 m_pSerializer->startElementNS(XML_w, XML_ruby);
3910 m_pSerializer->startElementNS(XML_w, XML_rubyPr);
3912 m_pSerializer->singleElementNS( XML_w, XML_rubyAlign,
3913 FSNS( XML_w, XML_val ), lclConvertWW8JCToOOXMLRubyAlign(aWW8Ruby.GetJC()) );
3914 sal_uInt32 nHps = (aWW8Ruby.GetRubyHeight() + 5) / 10;
3915 sal_uInt32 nHpsBaseText = (aWW8Ruby.GetBaseHeight() + 5) / 10;
3916 m_pSerializer->singleElementNS(XML_w, XML_hps, FSNS(XML_w, XML_val), OString::number(nHps));
3918 m_pSerializer->singleElementNS( XML_w, XML_hpsRaise,
3919 FSNS( XML_w, XML_val ), OString::number(nHpsBaseText) );
3921 m_pSerializer->singleElementNS( XML_w, XML_hpsBaseText,
3922 FSNS( XML_w, XML_val ), OString::number(nHpsBaseText) );
3924 lang::Locale aLocale( SwBreakIt::Get()->GetLocale(
3925 rNode.GetLang( nPos ) ) );
3926 OUString sLang( LanguageTag::convertToBcp47( aLocale) );
3927 m_pSerializer->singleElementNS(XML_w, XML_lid, FSNS(XML_w, XML_val), sLang);
3929 m_pSerializer->endElementNS( XML_w, XML_rubyPr );
3931 m_pSerializer->startElementNS(XML_w, XML_rt);
3932 StartRun( nullptr, nPos );
3933 StartRunProperties( );
3935 if (rRuby.GetTextRuby() && rRuby.GetTextRuby()->GetCharFormat())
3937 const SwCharFormat* pFormat = rRuby.GetTextRuby()->GetCharFormat();
3938 sal_uInt16 nScript = g_pBreakIt->GetBreakIter()->getScriptType(rRuby.GetText(), 0);
3939 TypedWhichId<SvxFontItem> nWhichFont = (nScript == i18n::ScriptType::LATIN) ? RES_CHRATR_FONT : RES_CHRATR_CJK_FONT;
3940 TypedWhichId<SvxFontHeightItem> nWhichFontSize = (nScript == i18n::ScriptType::LATIN) ? RES_CHRATR_FONTSIZE : RES_CHRATR_CJK_FONTSIZE;
3942 CharFont(pFormat->GetFormatAttr(nWhichFont));
3943 CharFontSize(pFormat->GetFormatAttr(nWhichFontSize));
3944 CharFontSize(pFormat->GetFormatAttr(RES_CHRATR_CTL_FONTSIZE));
3947 EndRunProperties( nullptr );
3948 RunText( rRuby.GetText( ) );
3949 EndRun( &rNode, nPos, -1 );
3950 m_pSerializer->endElementNS( XML_w, XML_rt );
3952 m_pSerializer->startElementNS(XML_w, XML_rubyBase);
3953 StartRun( nullptr, nPos );
3956 void DocxAttributeOutput::EndRuby(const SwTextNode& rNode, sal_Int32 nPos)
3958 SAL_INFO("sw.ww8", "TODO DocxAttributeOutput::EndRuby()" );
3959 EndRun( &rNode, nPos, -1 );
3960 m_pSerializer->endElementNS( XML_w, XML_rubyBase );
3961 m_pSerializer->endElementNS( XML_w, XML_ruby );
3962 m_pSerializer->endElementNS( XML_w, XML_r );
3963 StartRun(nullptr, nPos); // open Run again so OutputTextNode loop can close it
3966 bool DocxAttributeOutput::AnalyzeURL( const OUString& rUrl, const OUString& rTarget, OUString* pLinkURL, OUString* pMark )
3968 bool bBookMarkOnly = AttributeOutputBase::AnalyzeURL( rUrl, rTarget, pLinkURL, pMark );
3969 if (bBookMarkOnly)
3970 *pMark = GetExport().BookmarkToWord(*pMark);
3972 if (!pMark->isEmpty() && (bBookMarkOnly || rTarget.isEmpty()))
3974 OUString sURL = *pLinkURL;
3976 if ( bBookMarkOnly )
3977 sURL = FieldString( ww::eHYPERLINK );
3978 else
3979 sURL = FieldString( ww::eHYPERLINK ) + "\"" + sURL + "\"";
3981 sURL += " \\l \"" + *pMark + "\"";
3983 if ( !rTarget.isEmpty() )
3984 sURL += " \\n " + rTarget;
3986 *pLinkURL = sURL;
3989 return bBookMarkOnly;
3992 void DocxAttributeOutput::WriteBookmarkInActParagraph( const OUString& rName, sal_Int32 nFirstRunPos, sal_Int32 nLastRunPos )
3994 m_aBookmarksOfParagraphStart.insert(std::pair<sal_Int32, OUString>(nFirstRunPos, rName));
3995 m_aBookmarksOfParagraphEnd.insert(std::pair<sal_Int32, OUString>(nLastRunPos, rName));
3998 bool DocxAttributeOutput::StartURL(const OUString& rUrl, const OUString& rTarget, const OUString& rName)
4000 OUString sMark;
4001 OUString sUrl;
4003 bool bBookmarkOnly = AnalyzeURL( rUrl, rTarget, &sUrl, &sMark );
4005 m_hyperLinkAnchor = sMark;
4007 if (!sMark.isEmpty() && !bBookmarkOnly && rTarget.isEmpty())
4009 m_rExport.OutputField( nullptr, ww::eHYPERLINK, sUrl );
4011 else
4013 // Output a hyperlink XML element
4014 m_pHyperlinkAttrList = FastSerializerHelper::createAttrList();
4016 if ( !bBookmarkOnly )
4018 OUString sId = GetExport().GetFilter().addRelation( m_pSerializer->getOutputStream(),
4019 oox::getRelationship(Relationship::HYPERLINK),
4020 sUrl, true );
4022 m_pHyperlinkAttrList->add(FSNS(XML_r, XML_id), sId);
4023 if (!sMark.isEmpty())
4025 sMark = sMark.replace(' ', '_');
4026 m_pHyperlinkAttrList->add(FSNS(XML_w, XML_anchor), sMark);
4029 else
4031 // Is this a link to a sequence? Then try to replace that with a
4032 // normal bookmark, as Word won't understand our special
4033 // <seqname>!<index>|sequence syntax.
4034 if (sMark.endsWith("|sequence"))
4036 sal_Int32 nPos = sMark.indexOf('!');
4037 if (nPos != -1)
4039 // Extract <seqname>, the field instruction text has the name quoted.
4040 OUString aSequenceName = sMark.copy(0, nPos);
4041 // Extract <index>.
4042 sal_uInt32 nIndex = o3tl::toUInt32(sMark.subView(nPos + 1, sMark.getLength() - nPos - sizeof("|sequence")));
4043 auto it = m_aSeqBookmarksNames.find(aSequenceName);
4044 if (it != m_aSeqBookmarksNames.end())
4046 std::vector<OUString>& rNames = it->second;
4047 if (rNames.size() > nIndex)
4048 // We know the bookmark name for this sequence and this index, do the replacement.
4049 sMark = rNames[nIndex];
4053 else if (sMark.endsWith("|toxmark"))
4055 if (auto const it = GetExport().m_TOXMarkBookmarksByURL.find(sMark);
4056 it != GetExport().m_TOXMarkBookmarksByURL.end())
4058 sMark = it->second;
4061 // Spaces are prohibited in bookmark name.
4062 sMark = sMark.replace(' ', '_');
4063 m_pHyperlinkAttrList->add( FSNS( XML_w, XML_anchor ), sMark );
4066 if ( !rTarget.isEmpty() )
4068 m_pHyperlinkAttrList->add(FSNS(XML_w, XML_tgtFrame), rTarget);
4070 else if (!rName.isEmpty())
4072 m_pHyperlinkAttrList->add(FSNS(XML_w, XML_tooltip), rName);
4076 return true;
4079 bool DocxAttributeOutput::EndURL(bool const)
4081 m_closeHyperlinkInThisRun = true;
4082 if (m_nHyperLinkCount.back() > 0 && !m_hyperLinkAnchor.isEmpty()
4083 && m_hyperLinkAnchor.startsWith("_Toc"))
4085 m_endPageRef = true;
4087 return true;
4090 void DocxAttributeOutput::FieldVanish(const OUString& rText,
4091 ww::eField const eType, OUString const*const pBookmarkName)
4093 WriteField_Impl(nullptr, eType, rText, FieldFlags::All, pBookmarkName);
4096 // The difference between 'Redline' and 'StartRedline'+'EndRedline' is that:
4097 // 'Redline' is used for tracked changes of formatting information of a run like Bold, Underline. (the '<w:rPrChange>' is inside the 'run' node)
4098 // 'StartRedline' is used to output tracked changes of run insertion and deletion (the run is inside the '<w:ins>' node)
4099 void DocxAttributeOutput::Redline( const SwRedlineData* pRedlineData)
4101 if ( !pRedlineData )
4102 return;
4104 bool bRemovePersonalInfo = SvtSecurityOptions::IsOptionSet(
4105 SvtSecurityOptions::EOption::DocWarnRemovePersonalInfo ) && !SvtSecurityOptions::IsOptionSet(
4106 SvtSecurityOptions::EOption::DocWarnKeepRedlineInfo);
4108 OString aId( OString::number( pRedlineData->GetSeqNo() ) );
4109 const OUString& rAuthor(SwModule::get()->GetRedlineAuthor(pRedlineData->GetAuthor()));
4110 const DateTime& aDateTime = pRedlineData->GetTimeStamp();
4111 bool bNoDate = bRemovePersonalInfo ||
4112 ( aDateTime.GetYear() == 1970 && aDateTime.GetMonth() == 1 && aDateTime.GetDay() == 1 );
4114 switch( pRedlineData->GetType() )
4116 case RedlineType::Insert:
4117 break;
4119 case RedlineType::Delete:
4120 break;
4122 case RedlineType::Format:
4124 rtl::Reference<sax_fastparser::FastAttributeList> pAttributeList
4125 = sax_fastparser::FastSerializerHelper::createAttrList();
4127 pAttributeList->add(FSNS( XML_w, XML_id ), aId);
4128 pAttributeList->add(FSNS( XML_w, XML_author ), bRemovePersonalInfo
4129 ? "Author" + OString::number( GetExport().GetInfoID(rAuthor) )
4130 : rAuthor.toUtf8());
4131 if (!bNoDate)
4132 pAttributeList->add(FSNS( XML_w, XML_date ), DateTimeToOString( aDateTime ));
4133 m_pSerializer->startElementNS( XML_w, XML_rPrChange, pAttributeList );
4135 // Check if there is any extra data stored in the redline object
4136 if (pRedlineData->GetExtraData())
4138 const SwRedlineExtraData* pExtraData = pRedlineData->GetExtraData();
4139 const SwRedlineExtraData_FormatColl* pFormattingChanges = dynamic_cast<const SwRedlineExtraData_FormatColl*>(pExtraData);
4141 // Check if the extra data is of type 'formatting changes'
4142 if (pFormattingChanges)
4144 // Get the item set that holds all the changes properties
4145 const SfxItemSet *pChangesSet = pFormattingChanges->GetItemSet();
4146 if (pChangesSet)
4148 m_pSerializer->mark(Tag_Redline_1);
4150 m_pSerializer->startElementNS(XML_w, XML_rPr);
4152 // Output the redline item set
4153 m_rExport.OutputItemSet( *pChangesSet, false, true, i18n::ScriptType::LATIN, m_rExport.m_bExportModeRTF );
4155 m_pSerializer->endElementNS( XML_w, XML_rPr );
4157 m_pSerializer->mergeTopMarks(Tag_Redline_1, sax_fastparser::MergeMarks::PREPEND);
4162 m_pSerializer->endElementNS( XML_w, XML_rPrChange );
4163 break;
4165 case RedlineType::ParagraphFormat:
4167 rtl::Reference<sax_fastparser::FastAttributeList> pAttributeList
4168 = sax_fastparser::FastSerializerHelper::createAttrList();
4170 pAttributeList->add(FSNS( XML_w, XML_id ), aId);
4171 pAttributeList->add(FSNS( XML_w, XML_author ), bRemovePersonalInfo
4172 ? "Author" + OString::number( GetExport().GetInfoID(rAuthor) )
4173 : rAuthor.toUtf8());
4174 if (!bNoDate)
4175 pAttributeList->add(FSNS( XML_w, XML_date ), DateTimeToOString( aDateTime ));
4176 m_pSerializer->startElementNS( XML_w, XML_pPrChange, pAttributeList );
4178 // Check if there is any extra data stored in the redline object
4179 if (pRedlineData->GetExtraData())
4181 const SwRedlineExtraData* pExtraData = pRedlineData->GetExtraData();
4182 const SwRedlineExtraData_FormatColl* pFormattingChanges = dynamic_cast<const SwRedlineExtraData_FormatColl*>(pExtraData);
4184 // Check if the extra data is of type 'formatting changes'
4185 if (pFormattingChanges)
4187 // Get the item set that holds all the changes properties
4188 const SfxItemSet *pChangesSet = pFormattingChanges->GetItemSet();
4189 const OUString & sParaStyleName = pFormattingChanges->GetFormatName();
4190 if (pChangesSet || !sParaStyleName.isEmpty())
4192 m_pSerializer->mark(Tag_Redline_2);
4194 m_pSerializer->startElementNS(XML_w, XML_pPr);
4196 if (!sParaStyleName.isEmpty())
4198 OString sStyleName;
4199 if (auto format = m_rExport.m_rDoc.FindTextFormatCollByName(sParaStyleName))
4200 if (auto slot = m_rExport.m_pStyles->GetSlot(format); slot != 0xfff)
4201 sStyleName = m_rExport.m_pStyles->GetStyleId(slot);
4202 // The resolved style name can be empty at this point, sParaStyleName can be
4203 // an arbitrary string from the original document.
4204 // Note that Word does *not* roundtrip unknown style names in redlines!
4205 if (sStyleName.isEmpty())
4206 sStyleName = MSWordStyles::CreateStyleId(sParaStyleName);
4207 if (!sStyleName.isEmpty())
4208 m_pSerializer->singleElementNS(XML_w, XML_pStyle, FSNS(XML_w, XML_val), sStyleName);
4211 // The 'm_rExport.SdrExporter().getFlyAttrList()', 'm_pParagraphSpacingAttrList' are used to hold information
4212 // that should be collected by different properties in the core, and are all flushed together
4213 // to the DOCX when the function 'WriteCollectedParagraphProperties' gets called.
4214 // So we need to store the current status of these lists, so that we can revert back to them when
4215 // we are done exporting the redline attributes.
4216 auto pFlyAttrList_Original(detachFrom(m_rExport.SdrExporter().getFlyAttrList()));
4217 auto pLRSpaceAttrList_Original(detachFrom(m_pLRSpaceAttrList));
4218 auto pParagraphSpacingAttrList_Original(detachFrom(m_pParagraphSpacingAttrList));
4220 // Output the redline item set
4221 if (pChangesSet)
4222 m_rExport.OutputItemSet( *pChangesSet, true, false, i18n::ScriptType::LATIN, m_rExport.m_bExportModeRTF );
4224 // Write the collected paragraph properties that are stored in 'm_rExport.SdrExporter().getFlyAttrList()', 'm_pParagraphSpacingAttrList'
4225 WriteCollectedParagraphProperties();
4227 // Revert back the original values that were stored in 'm_rExport.SdrExporter().getFlyAttrList()', 'm_pParagraphSpacingAttrList'
4228 m_rExport.SdrExporter().getFlyAttrList() = std::move(pFlyAttrList_Original);
4229 m_pLRSpaceAttrList = std::move(pLRSpaceAttrList_Original);
4230 m_pParagraphSpacingAttrList = std::move(pParagraphSpacingAttrList_Original);
4232 m_pSerializer->endElementNS( XML_w, XML_pPr );
4234 m_pSerializer->mergeTopMarks(Tag_Redline_2, sax_fastparser::MergeMarks::PREPEND);
4238 m_pSerializer->endElementNS( XML_w, XML_pPrChange );
4239 break;
4241 default:
4242 SAL_WARN("sw.ww8", "Unhandled redline type for export " << SwRedlineTypeToOUString(pRedlineData->GetType()));
4243 break;
4247 // The difference between 'Redline' and 'StartRedline'+'EndRedline' is that:
4248 // 'Redline' is used for tracked changes of formatting information of a run like Bold, Underline. (the '<w:rPrChange>' is inside the 'run' node)
4249 // 'StartRedline' is used to output tracked changes of run insertion and deletion (the run is inside the '<w:ins>' node)
4250 void DocxAttributeOutput::StartRedline( const SwRedlineData * pRedlineData, bool bLastRun )
4252 if ( !pRedlineData )
4253 return;
4255 // write out stack of this redline recursively (first the oldest)
4256 if ( !bLastRun )
4257 StartRedline( pRedlineData->Next(), false );
4259 OString aId( OString::number( m_nRedlineId++ ) );
4261 bool bRemovePersonalInfo = SvtSecurityOptions::IsOptionSet(
4262 SvtSecurityOptions::EOption::DocWarnRemovePersonalInfo ) && !SvtSecurityOptions::IsOptionSet(
4263 SvtSecurityOptions::EOption::DocWarnKeepRedlineInfo);
4265 const OUString& rAuthor(SwModule::get()->GetRedlineAuthor(pRedlineData->GetAuthor()));
4266 OString aAuthor( OUStringToOString( bRemovePersonalInfo
4267 ? "Author" + OUString::number( GetExport().GetInfoID(rAuthor) )
4268 : rAuthor, RTL_TEXTENCODING_UTF8 ) );
4270 const DateTime& aDateTime = pRedlineData->GetTimeStamp();
4271 bool bNoDate = bRemovePersonalInfo ||
4272 ( aDateTime.GetYear() == 1970 && aDateTime.GetMonth() == 1 && aDateTime.GetDay() == 1 );
4273 bool bMoved = pRedlineData->IsMoved() &&
4274 // tdf#150166 save tracked moving around TOC as w:ins, w:del
4275 SwDoc::GetCurTOX(*m_rExport.m_pCurPam->GetPoint()) == nullptr;
4276 switch ( pRedlineData->GetType() )
4278 case RedlineType::Insert:
4279 case RedlineType::Delete:
4281 sal_Int32 eElement = RedlineType::Insert == pRedlineData->GetType()
4282 ? ( bMoved ? XML_moveTo : XML_ins )
4283 : ( bMoved ? XML_moveFrom : XML_del );
4284 if ( bNoDate )
4285 m_pSerializer->startElementNS( XML_w, eElement,
4286 FSNS( XML_w, XML_id ), aId,
4287 FSNS( XML_w, XML_author ), aAuthor );
4288 else
4289 m_pSerializer->startElementNS( XML_w, eElement,
4290 FSNS( XML_w, XML_id ), aId,
4291 FSNS( XML_w, XML_author ), aAuthor,
4292 FSNS( XML_w, XML_date ), DateTimeToOString( aDateTime ) );
4293 break;
4295 case RedlineType::Format:
4296 SAL_INFO("sw.ww8", "TODO DocxAttributeOutput::StartRedline()" );
4297 break;
4298 default:
4299 break;
4303 void DocxAttributeOutput::EndRedline( const SwRedlineData * pRedlineData, bool bLastRun )
4305 if ( !pRedlineData || m_bWritingField )
4306 return;
4308 bool bMoved = pRedlineData->IsMoved() &&
4309 // tdf#150166 save tracked moving around TOC as w:ins, w:del
4310 SwDoc::GetCurTOX(*m_rExport.m_pCurPam->GetPoint()) == nullptr;
4311 switch ( pRedlineData->GetType() )
4313 case RedlineType::Insert:
4314 m_pSerializer->endElementNS( XML_w, bMoved ? XML_moveTo : XML_ins );
4315 break;
4317 case RedlineType::Delete:
4318 m_pSerializer->endElementNS( XML_w, bMoved ? XML_moveFrom : XML_del );
4319 break;
4321 case RedlineType::Format:
4322 SAL_INFO("sw.ww8", "TODO DocxAttributeOutput::EndRedline()" );
4323 break;
4324 default:
4325 break;
4328 // write out stack of this redline recursively (first the newest)
4329 if ( !bLastRun )
4330 EndRedline( pRedlineData->Next(), false );
4333 void DocxAttributeOutput::FormatDrop( const SwTextNode& /*rNode*/, const SwFormatDrop& /*rSwFormatDrop*/, sal_uInt16 /*nStyle*/, ww8::WW8TableNodeInfo::Pointer_t /*pTextNodeInfo*/, ww8::WW8TableNodeInfoInner::Pointer_t )
4335 SAL_INFO("sw.ww8", "TODO DocxAttributeOutput::FormatDrop( const SwTextNode& rNode, const SwFormatDrop& rSwFormatDrop, sal_uInt16 nStyle )" );
4338 void DocxAttributeOutput::ParagraphStyle( sal_uInt16 nStyle )
4340 OString aStyleId(m_rExport.m_pStyles->GetStyleId(nStyle));
4342 m_pSerializer->singleElementNS(XML_w, XML_pStyle, FSNS(XML_w, XML_val), aStyleId);
4345 static void impl_borderLine( FSHelperPtr const & pSerializer, sal_Int32 elementToken, const SvxBorderLine* pBorderLine, sal_uInt16 nDist,
4346 bool bWriteShadow, const table::BorderLine2* pStyleProps = nullptr)
4348 // Compute val attribute value
4349 // Can be one of:
4350 // single, double,
4351 // basicWideOutline, basicWideInline
4352 // OOXml also supports those types of borders, but we'll try to play with the first ones.
4353 // thickThinMediumGap, thickThinLargeGap, thickThinSmallGap
4354 // thinThickLargeGap, thinThickMediumGap, thinThickSmallGap
4355 const char* pVal = "nil";
4356 if ( pBorderLine && !pBorderLine->isEmpty( ) )
4358 switch (pBorderLine->GetBorderLineStyle())
4360 case SvxBorderLineStyle::SOLID:
4361 pVal = "single";
4362 break;
4363 case SvxBorderLineStyle::DOTTED:
4364 pVal = "dotted";
4365 break;
4366 case SvxBorderLineStyle::DASHED:
4367 pVal = "dashed";
4368 break;
4369 case SvxBorderLineStyle::DOUBLE:
4370 case SvxBorderLineStyle::DOUBLE_THIN:
4371 pVal = "double";
4372 break;
4373 case SvxBorderLineStyle::THINTHICK_SMALLGAP:
4374 pVal = "thinThickSmallGap";
4375 break;
4376 case SvxBorderLineStyle::THINTHICK_MEDIUMGAP:
4377 pVal = "thinThickMediumGap";
4378 break;
4379 case SvxBorderLineStyle::THINTHICK_LARGEGAP:
4380 pVal = "thinThickLargeGap";
4381 break;
4382 case SvxBorderLineStyle::THICKTHIN_SMALLGAP:
4383 pVal = "thickThinSmallGap";
4384 break;
4385 case SvxBorderLineStyle::THICKTHIN_MEDIUMGAP:
4386 pVal = "thickThinMediumGap";
4387 break;
4388 case SvxBorderLineStyle::THICKTHIN_LARGEGAP:
4389 pVal = "thickThinLargeGap";
4390 break;
4391 case SvxBorderLineStyle::EMBOSSED:
4392 pVal = "threeDEmboss";
4393 break;
4394 case SvxBorderLineStyle::ENGRAVED:
4395 pVal = "threeDEngrave";
4396 break;
4397 case SvxBorderLineStyle::OUTSET:
4398 pVal = "outset";
4399 break;
4400 case SvxBorderLineStyle::INSET:
4401 pVal = "inset";
4402 break;
4403 case SvxBorderLineStyle::FINE_DASHED:
4404 pVal = "dashSmallGap";
4405 break;
4406 case SvxBorderLineStyle::DASH_DOT:
4407 pVal = "dotDash";
4408 break;
4409 case SvxBorderLineStyle::DASH_DOT_DOT:
4410 pVal = "dotDotDash";
4411 break;
4412 case SvxBorderLineStyle::NONE:
4413 default:
4414 break;
4417 else if (!pStyleProps || !pStyleProps->LineWidth)
4418 // no line, and no line set by the style either:
4419 // there is no need to write the property
4420 return;
4422 // compare the properties with the theme properties before writing them:
4423 // if they are equal, it means that they were style-defined and there is
4424 // no need to write them.
4425 if (pStyleProps && pBorderLine && !pBorderLine->isEmpty()
4426 && pBorderLine->GetBorderLineStyle()
4427 == static_cast<SvxBorderLineStyle>(pStyleProps->LineStyle)
4428 && pBorderLine->GetColor() == Color(ColorTransparency, pStyleProps->Color)
4429 && pBorderLine->GetWidth() == o3tl::toTwips(pStyleProps->LineWidth, o3tl::Length::mm100))
4431 return;
4434 rtl::Reference<FastAttributeList> pAttr = FastSerializerHelper::createAttrList();
4435 pAttr->add( FSNS( XML_w, XML_val ), pVal );
4437 if ( pBorderLine && !pBorderLine->isEmpty() )
4439 // Compute the sz attribute
4441 double const fConverted( ::editeng::ConvertBorderWidthToWord(
4442 pBorderLine->GetBorderLineStyle(), pBorderLine->GetWidth()));
4443 // The unit is the 8th of point
4444 sal_Int32 nWidth = sal_Int32( fConverted / 2.5 );
4445 const sal_Int32 nMinWidth = 2;
4446 const sal_Int32 nMaxWidth = 96;
4448 if ( nWidth > nMaxWidth )
4449 nWidth = nMaxWidth;
4450 else if ( nWidth < nMinWidth )
4451 nWidth = nMinWidth;
4453 pAttr->add( FSNS( XML_w, XML_sz ), OString::number( nWidth ) );
4455 // Get the distance (in pt)
4456 pAttr->add(FSNS(XML_w, XML_space), OString::number(rtl::math::round(nDist / 20.0)));
4458 // Get the color code as an RRGGBB hex value
4459 OString sColor( msfilter::util::ConvertColor( pBorderLine->GetColor( ) ) );
4460 pAttr->add( FSNS(XML_w, XML_color), sColor);
4462 model::ComplexColor const& rComplexColor = pBorderLine->getComplexColor();
4463 lclAddThemeColorAttributes(pAttr, rComplexColor);
4466 if (bWriteShadow)
4468 // Set the shadow value
4469 pAttr->add( FSNS( XML_w, XML_shadow ), "1" );
4472 pSerializer->singleElementNS( XML_w, elementToken, pAttr );
4475 static OutputBorderOptions lcl_getTableCellBorderOptions(bool bEcma)
4477 OutputBorderOptions rOptions;
4479 rOptions.tag = XML_tcBorders;
4480 rOptions.bUseStartEnd = !bEcma;
4481 rOptions.bWriteTag = true;
4482 rOptions.bWriteDistance = false;
4484 return rOptions;
4487 static OutputBorderOptions lcl_getBoxBorderOptions()
4489 OutputBorderOptions rOptions;
4491 rOptions.tag = XML_pBdr;
4492 rOptions.bUseStartEnd = false;
4493 rOptions.bWriteTag = false;
4494 rOptions.bWriteDistance = true;
4496 return rOptions;
4499 static void impl_borders( FSHelperPtr const & pSerializer,
4500 const SvxBoxItem& rBox,
4501 const OutputBorderOptions& rOptions,
4502 std::map<SvxBoxItemLine,
4503 css::table::BorderLine2> &rTableStyleConf,
4504 ww8::Frame* pFramePr = nullptr)
4506 static const SvxBoxItemLine aBorders[] =
4508 SvxBoxItemLine::TOP, SvxBoxItemLine::LEFT, SvxBoxItemLine::BOTTOM, SvxBoxItemLine::RIGHT
4511 const sal_Int32 aXmlElements[] =
4513 XML_top,
4514 rOptions.bUseStartEnd ? XML_start : XML_left,
4515 XML_bottom,
4516 rOptions.bUseStartEnd ? XML_end : XML_right
4518 bool tagWritten = false;
4519 const SvxBoxItemLine* pBrd = aBorders;
4521 for( int i = 0; i < 4; ++i, ++pBrd )
4523 const SvxBorderLine* pLn = rBox.GetLine( *pBrd );
4524 const table::BorderLine2 *aStyleProps = nullptr;
4525 auto it = rTableStyleConf.find( *pBrd );
4526 if( it != rTableStyleConf.end() )
4527 aStyleProps = &(it->second);
4529 if (!tagWritten && rOptions.bWriteTag)
4531 pSerializer->startElementNS(XML_w, rOptions.tag);
4532 tagWritten = true;
4535 bool bWriteShadow = false;
4536 if (rOptions.aShadowLocation == SvxShadowLocation::NONE)
4538 // The border has no shadow
4540 else if (rOptions.aShadowLocation == SvxShadowLocation::BottomRight)
4542 // Special case of 'Bottom-Right' shadow:
4543 // If the shadow location is 'Bottom-Right' - then turn on the shadow
4544 // for ALL the sides. This is because in Word - if you select a shadow
4545 // for a border - it turn on the shadow for ALL the sides (but shows only
4546 // the bottom-right one).
4547 // This is so that no information will be lost if passed through LibreOffice
4548 bWriteShadow = true;
4550 else
4552 // If there is a shadow, and it's not the regular 'Bottom-Right',
4553 // then write only the 'shadowed' sides of the border
4554 if (
4555 ((rOptions.aShadowLocation == SvxShadowLocation::TopLeft || rOptions.aShadowLocation == SvxShadowLocation::TopRight ) && *pBrd == SvxBoxItemLine::TOP ) ||
4556 ((rOptions.aShadowLocation == SvxShadowLocation::TopLeft || rOptions.aShadowLocation == SvxShadowLocation::BottomLeft) && *pBrd == SvxBoxItemLine::LEFT ) ||
4557 ((rOptions.aShadowLocation == SvxShadowLocation::BottomLeft ) && *pBrd == SvxBoxItemLine::BOTTOM) ||
4558 ((rOptions.aShadowLocation == SvxShadowLocation::TopRight ) && *pBrd == SvxBoxItemLine::RIGHT )
4561 bWriteShadow = true;
4565 sal_uInt16 nDist = 0;
4566 if (rOptions.bWriteDistance)
4568 if (rOptions.pDistances)
4570 if ( *pBrd == SvxBoxItemLine::TOP)
4571 nDist = rOptions.pDistances->nTop;
4572 else if ( *pBrd == SvxBoxItemLine::LEFT)
4573 nDist = rOptions.pDistances->nLeft;
4574 else if ( *pBrd == SvxBoxItemLine::BOTTOM)
4575 nDist = rOptions.pDistances->nBottom;
4576 else if ( *pBrd == SvxBoxItemLine::RIGHT)
4577 nDist = rOptions.pDistances->nRight;
4579 else
4581 nDist = rBox.GetDistance(*pBrd);
4585 if (pFramePr)
4587 assert(rOptions.bWriteDistance && !rOptions.pDistances);
4589 // In addition to direct properties, and paragraph styles,
4590 // for framePr-floated paragraphs the frame borders also affect the exported values.
4592 // For border spacing, there is a special situation to consider
4593 // because a compat setting ignores left/right paragraph spacing on layout.
4594 const SwFrameFormat& rFormat = pFramePr->GetFrameFormat();
4595 const SvxBoxItem& rFramePrBox = rFormat.GetBox();
4596 const IDocumentSettingAccess& rIDSA = rFormat.GetDoc()->getIDocumentSettingAccess();
4597 if (rIDSA.get(DocumentSettingId::INVERT_BORDER_SPACING)
4598 && (*pBrd == SvxBoxItemLine::LEFT || *pBrd == SvxBoxItemLine::RIGHT))
4600 // only the frame's border spacing affects layout - so use that value instead.
4601 nDist = rFramePrBox.GetDistance(*pBrd);
4603 else
4605 nDist += rFramePrBox.GetDistance(*pBrd);
4608 // Unless the user added a paragraph border, the border normally comes from the frame.
4609 if (!pLn)
4610 pLn = rFramePrBox.GetLine(*pBrd);
4613 impl_borderLine( pSerializer, aXmlElements[i], pLn, nDist, bWriteShadow, aStyleProps );
4615 if (tagWritten && rOptions.bWriteTag) {
4616 pSerializer->endElementNS( XML_w, rOptions.tag );
4620 void DocxAttributeOutput::ImplCellMargins( FSHelperPtr const & pSerializer, const SvxBoxItem& rBox, sal_Int32 tag, bool bUseStartEnd, const SvxBoxItem* pDefaultMargins)
4622 static const SvxBoxItemLine aBorders[] =
4624 SvxBoxItemLine::TOP, SvxBoxItemLine::LEFT, SvxBoxItemLine::BOTTOM, SvxBoxItemLine::RIGHT
4627 const sal_Int32 aXmlElements[] =
4629 XML_top,
4630 bUseStartEnd ? XML_start : XML_left,
4631 XML_bottom,
4632 bUseStartEnd ? XML_end : XML_right
4634 bool tagWritten = false;
4635 const SvxBoxItemLine* pBrd = aBorders;
4636 for( int i = 0; i < 4; ++i, ++pBrd )
4638 sal_Int32 nDist = sal_Int32( rBox.GetDistance( *pBrd ) );
4640 if (pDefaultMargins)
4642 // Skip output if cell margin == table default margin
4643 if (sal_Int32( pDefaultMargins->GetDistance( *pBrd ) ) == nDist)
4644 continue;
4647 if (!tagWritten) {
4648 pSerializer->startElementNS(XML_w, tag);
4649 tagWritten = true;
4651 pSerializer->singleElementNS( XML_w, aXmlElements[i],
4652 FSNS( XML_w, XML_w ), OString::number(nDist),
4653 FSNS( XML_w, XML_type ), "dxa" );
4655 if (tagWritten) {
4656 pSerializer->endElementNS( XML_w, tag );
4660 void DocxAttributeOutput::TableCellProperties( ww8::WW8TableNodeInfoInner::Pointer_t const & pTableTextNodeInfoInner, sal_uInt32 nCell, sal_uInt32 nRow )
4662 m_pSerializer->startElementNS(XML_w, XML_tcPr);
4664 const SwTableBox *pTableBox = pTableTextNodeInfoInner->getTableBox( );
4666 bool const bEcma = GetExport().GetFilter().getVersion() == oox::core::ECMA_376_1ST_EDITION;
4668 // Output any table cell redlines if there are any attached to this specific cell
4669 TableCellRedline( pTableTextNodeInfoInner );
4671 // Cell preferred width
4672 SwTwips nWidth = GetGridCols( pTableTextNodeInfoInner )->at( nCell );
4673 if ( nCell )
4674 nWidth = nWidth - GetGridCols( pTableTextNodeInfoInner )->at( nCell - 1 );
4675 m_pSerializer->singleElementNS( XML_w, XML_tcW,
4676 FSNS( XML_w, XML_w ), OString::number(nWidth),
4677 FSNS( XML_w, XML_type ), "dxa" );
4679 // Horizontal spans
4680 const SwWriteTableRows& rRows = m_xTableWrt->GetRows( );
4681 if (nRow >= rRows.size())
4682 SAL_WARN("sw.ww8", "DocxAttributeOutput::TableCellProperties: out of range row: " << nRow);
4683 else
4685 SwWriteTableRow *pRow = rRows[ nRow ].get();
4686 const SwWriteTableCells& rTableCells = pRow->GetCells();
4687 if (nCell < rTableCells.size() )
4689 const SwWriteTableCell& rCell = *rTableCells[nCell];
4690 const sal_uInt16 nColSpan = rCell.GetColSpan();
4691 if ( nColSpan > 1 )
4692 m_pSerializer->singleElementNS( XML_w, XML_gridSpan,
4693 FSNS( XML_w, XML_val ), OString::number(nColSpan) );
4697 // Vertical merges
4698 ww8::RowSpansPtr xRowSpans = pTableTextNodeInfoInner->getRowSpansOfRow();
4699 sal_Int32 vSpan = (*xRowSpans)[nCell];
4700 if ( vSpan > 1 )
4702 m_pSerializer->singleElementNS(XML_w, XML_vMerge, FSNS(XML_w, XML_val), "restart");
4704 else if ( vSpan < 0 )
4706 m_pSerializer->singleElementNS(XML_w, XML_vMerge, FSNS(XML_w, XML_val), "continue");
4709 if (const SfxGrabBagItem* pItem = pTableBox->GetFrameFormat()->GetAttrSet().GetItem<SfxGrabBagItem>(RES_FRMATR_GRABBAG))
4711 const std::map<OUString, uno::Any>& rGrabBag = pItem->GetGrabBag();
4712 std::map<OUString, uno::Any>::const_iterator it = rGrabBag.find(u"CellCnfStyle"_ustr);
4713 if (it != rGrabBag.end())
4715 uno::Sequence<beans::PropertyValue> aAttributes = it->second.get< uno::Sequence<beans::PropertyValue> >();
4716 m_pTableStyleExport->CnfStyle(aAttributes);
4721 const SvxBoxItem& rBox = pTableBox->GetFrameFormat( )->GetBox( );
4722 const SvxBoxItem& rDefaultBox = (*m_TableFirstCells.rbegin())->getTableBox( )->GetFrameFormat( )->GetBox( );
4724 // The cell borders
4725 impl_borders(m_pSerializer, rBox, lcl_getTableCellBorderOptions(bEcma),
4726 m_aTableStyleConfs.back());
4729 TableBackgrounds( pTableTextNodeInfoInner );
4732 // Cell margins
4733 DocxAttributeOutput::ImplCellMargins( m_pSerializer, rBox, XML_tcMar, !bEcma, &rDefaultBox );
4736 TableVerticalCell( pTableTextNodeInfoInner );
4738 m_pSerializer->endElementNS( XML_w, XML_tcPr );
4741 void DocxAttributeOutput::InitTableHelper( ww8::WW8TableNodeInfoInner::Pointer_t const & pTableTextNodeInfoInner )
4743 const SwTable* pTable = pTableTextNodeInfoInner->getTable();
4744 if (m_xTableWrt && pTable == m_xTableWrt->GetTable())
4745 return;
4747 tools::Long nPageSize = 0;
4748 bool bRelBoxSize = false;
4750 // Create the SwWriteTable instance to use col spans (and maybe other infos)
4751 GetTablePageSize( pTableTextNodeInfoInner.get(), nPageSize, bRelBoxSize );
4753 const SwFrameFormat *pFormat = pTable->GetFrameFormat( );
4754 const sal_uInt32 nTableSz = static_cast<sal_uInt32>(pFormat->GetFrameSize( ).GetWidth( ));
4756 const SwHTMLTableLayout *pLayout = pTable->GetHTMLTableLayout();
4757 if( pLayout && pLayout->IsExportable() )
4758 m_xTableWrt.reset(new SwWriteTable(pTable, pLayout));
4759 else
4760 m_xTableWrt.reset(new SwWriteTable(pTable, pTable->GetTabLines(), nPageSize, nTableSz, false));
4763 void DocxAttributeOutput::StartTable( ww8::WW8TableNodeInfoInner::Pointer_t const & pTableTextNodeInfoInner )
4765 m_aTableStyleConfs.emplace_back();
4767 // In case any paragraph SDT's are open, close them here.
4768 EndParaSdtBlock();
4770 m_pSerializer->startElementNS(XML_w, XML_tbl);
4772 m_TableFirstCells.push_back(pTableTextNodeInfoInner);
4773 m_LastOpenCell.push_back(-1);
4774 m_LastClosedCell.push_back(-1);
4776 InitTableHelper( pTableTextNodeInfoInner );
4777 TableDefinition( pTableTextNodeInfoInner );
4780 void DocxAttributeOutput::EndTable()
4782 m_pSerializer->endElementNS( XML_w, XML_tbl );
4784 if ( m_tableReference.m_nTableDepth > 0 )
4785 --m_tableReference.m_nTableDepth;
4787 m_LastClosedCell.pop_back();
4788 m_LastOpenCell.pop_back();
4789 m_TableFirstCells.pop_back();
4791 // We closed the table; if it is a nested table, the cell that contains it
4792 // still continues
4793 // set to true only if we were in a nested table, not otherwise.
4794 if( !m_TableFirstCells.empty() )
4795 m_tableReference.m_bTableCellOpen = true;
4797 // Cleans the table helper
4798 m_xTableWrt.reset();
4800 m_aTableStyleConfs.pop_back();
4803 void DocxAttributeOutput::StartTableRow( ww8::WW8TableNodeInfoInner::Pointer_t const & pTableTextNodeInfoInner )
4805 m_pSerializer->startElementNS(XML_w, XML_tr);
4807 // Output the row properties
4808 m_pSerializer->startElementNS(XML_w, XML_trPr);
4810 // Header row: tblHeader
4811 const SwTable *pTable = pTableTextNodeInfoInner->getTable( );
4812 if ( pTable->GetRowsToRepeat( ) > pTableTextNodeInfoInner->getRow( ) )
4813 m_pSerializer->singleElementNS(XML_w, XML_tblHeader, FSNS(XML_w, XML_val), "true"); // TODO to overwrite table style may need explicit false
4815 TableRowRedline( pTableTextNodeInfoInner );
4816 TableHeight( pTableTextNodeInfoInner );
4817 TableCanSplit( pTableTextNodeInfoInner );
4819 const SwTableBox *pTableBox = pTableTextNodeInfoInner->getTableBox();
4820 const SwTableLine* pTableLine = pTableBox->GetUpper();
4821 if (const SfxGrabBagItem* pItem = pTableLine->GetFrameFormat()->GetAttrSet().GetItem<SfxGrabBagItem>(RES_FRMATR_GRABBAG))
4823 const std::map<OUString, uno::Any>& rGrabBag = pItem->GetGrabBag();
4824 std::map<OUString, uno::Any>::const_iterator it = rGrabBag.find(u"RowCnfStyle"_ustr);
4825 if (it != rGrabBag.end())
4827 uno::Sequence<beans::PropertyValue> aAttributes = it->second.get< uno::Sequence<beans::PropertyValue> >();
4828 m_pTableStyleExport->CnfStyle(aAttributes);
4833 m_pSerializer->endElementNS( XML_w, XML_trPr );
4836 void DocxAttributeOutput::EndTableRow( )
4838 m_pSerializer->endElementNS( XML_w, XML_tr );
4839 m_LastOpenCell.back() = -1;
4840 m_LastClosedCell.back() = -1;
4843 void DocxAttributeOutput::StartTableCell( ww8::WW8TableNodeInfoInner::Pointer_t const & pTableTextNodeInfoInner, sal_uInt32 nCell, sal_uInt32 nRow )
4845 m_LastOpenCell.back() = nCell;
4847 InitTableHelper( pTableTextNodeInfoInner );
4849 // check tracked table column deletion or insertion
4850 const SwTableBox* pTabBox = pTableTextNodeInfoInner->getTableBox();
4851 SwRedlineTable::size_type nChange = pTabBox->GetRedline();
4852 if (nChange != SwRedlineTable::npos)
4853 m_tableReference.m_bTableCellChanged = true;
4855 m_pSerializer->startElementNS(XML_w, XML_tc);
4857 // Write the cell properties here
4858 TableCellProperties( pTableTextNodeInfoInner, nCell, nRow );
4860 m_tableReference.m_bTableCellOpen = true;
4863 void DocxAttributeOutput::EndTableCell(sal_uInt32 nCell)
4865 m_LastClosedCell.back() = nCell;
4866 m_LastOpenCell.back() = -1;
4868 if (m_tableReference.m_bTableCellParaSdtOpen)
4869 EndParaSdtBlock();
4871 m_pSerializer->endElementNS( XML_w, XML_tc );
4873 m_tableReference.m_bTableCellOpen = false;
4874 m_tableReference.m_bTableCellParaSdtOpen = false;
4875 m_tableReference.m_bTableCellChanged = false;
4878 void DocxAttributeOutput::StartStyles()
4880 m_pSerializer->startElementNS( XML_w, XML_styles,
4881 FSNS( XML_xmlns, XML_w ), GetExport().GetFilter().getNamespaceURL(OOX_NS(doc)),
4882 FSNS( XML_xmlns, XML_w14 ), GetExport().GetFilter().getNamespaceURL(OOX_NS(w14)),
4883 FSNS( XML_xmlns, XML_mc ), GetExport().GetFilter().getNamespaceURL(OOX_NS(mce)),
4884 FSNS( XML_mc, XML_Ignorable ), "w14" );
4886 DocDefaults();
4887 LatentStyles();
4890 sal_Int32 DocxStringGetToken(DocxStringTokenMap const * pMap, std::u16string_view rName)
4892 OString sName = OUStringToOString(rName, RTL_TEXTENCODING_UTF8);
4893 while (pMap->pToken)
4895 if (sName == pMap->pToken)
4896 return pMap->nToken;
4897 ++pMap;
4899 return 0;
4902 namespace
4905 DocxStringTokenMap const aDefaultTokens[] = {
4906 {"defQFormat", XML_defQFormat},
4907 {"defUnhideWhenUsed", XML_defUnhideWhenUsed},
4908 {"defSemiHidden", XML_defSemiHidden},
4909 {"count", XML_count},
4910 {"defUIPriority", XML_defUIPriority},
4911 {"defLockedState", XML_defLockedState},
4912 {nullptr, 0}
4915 DocxStringTokenMap const aExceptionTokens[] = {
4916 {"name", XML_name},
4917 {"locked", XML_locked},
4918 {"uiPriority", XML_uiPriority},
4919 {"semiHidden", XML_semiHidden},
4920 {"unhideWhenUsed", XML_unhideWhenUsed},
4921 {"qFormat", XML_qFormat},
4922 {nullptr, 0}
4927 void DocxAttributeOutput::LatentStyles()
4929 // Do we have latent styles available?
4930 rtl::Reference<SwXTextDocument> xPropertySet(m_rExport.m_rDoc.GetDocShell()->GetBaseModel());
4931 uno::Sequence<beans::PropertyValue> aInteropGrabBag;
4932 xPropertySet->getPropertyValue(u"InteropGrabBag"_ustr) >>= aInteropGrabBag;
4933 uno::Sequence<beans::PropertyValue> aLatentStyles;
4934 auto pProp = std::find_if(std::cbegin(aInteropGrabBag), std::cend(aInteropGrabBag),
4935 [](const beans::PropertyValue& rProp) { return rProp.Name == "latentStyles"; });
4936 if (pProp != std::cend(aInteropGrabBag))
4937 pProp->Value >>= aLatentStyles;
4938 if (!aLatentStyles.hasElements())
4939 return;
4941 // Extract default attributes first.
4942 rtl::Reference<sax_fastparser::FastAttributeList> pAttributeList = FastSerializerHelper::createAttrList();
4943 uno::Sequence<beans::PropertyValue> aLsdExceptions;
4944 for (const auto& rLatentStyle : aLatentStyles)
4946 if (sal_Int32 nToken = DocxStringGetToken(aDefaultTokens, rLatentStyle.Name))
4947 pAttributeList->add(FSNS(XML_w, nToken), rLatentStyle.Value.get<OUString>());
4948 else if (rLatentStyle.Name == "lsdExceptions")
4949 rLatentStyle.Value >>= aLsdExceptions;
4952 m_pSerializer->startElementNS(XML_w, XML_latentStyles, detachFrom(pAttributeList));
4954 // Then handle the exceptions.
4955 for (const auto& rLsdException : aLsdExceptions)
4957 pAttributeList = FastSerializerHelper::createAttrList();
4959 uno::Sequence<beans::PropertyValue> aAttributes;
4960 rLsdException.Value >>= aAttributes;
4961 for (const auto& rAttribute : aAttributes)
4962 if (sal_Int32 nToken = DocxStringGetToken(aExceptionTokens, rAttribute.Name))
4963 pAttributeList->add(FSNS(XML_w, nToken), rAttribute.Value.get<OUString>());
4965 m_pSerializer->singleElementNS(XML_w, XML_lsdException, detachFrom(pAttributeList));
4968 m_pSerializer->endElementNS(XML_w, XML_latentStyles);
4971 void DocxAttributeOutput::OutputDefaultItem(const SfxPoolItem& rHt)
4973 bool bMustWrite = true;
4974 switch (rHt.Which())
4976 case RES_CHRATR_CASEMAP:
4977 bMustWrite = rHt.StaticWhichCast(RES_CHRATR_CASEMAP).GetCaseMap() != SvxCaseMap::NotMapped;
4978 break;
4979 case RES_CHRATR_COLOR:
4980 bMustWrite = rHt.StaticWhichCast(RES_CHRATR_COLOR).GetValue() != COL_AUTO;
4981 break;
4982 case RES_CHRATR_CONTOUR:
4983 bMustWrite = rHt.StaticWhichCast(RES_CHRATR_CONTOUR).GetValue();
4984 break;
4985 case RES_CHRATR_CROSSEDOUT:
4986 bMustWrite = rHt.StaticWhichCast(RES_CHRATR_CROSSEDOUT).GetStrikeout() != STRIKEOUT_NONE;
4987 break;
4988 case RES_CHRATR_ESCAPEMENT:
4989 bMustWrite = rHt.StaticWhichCast(RES_CHRATR_ESCAPEMENT).GetEscapement() != SvxEscapement::Off;
4990 break;
4991 case RES_CHRATR_FONT:
4992 bMustWrite = true;
4993 break;
4994 case RES_CHRATR_FONTSIZE:
4995 bMustWrite = rHt.StaticWhichCast(RES_CHRATR_FONTSIZE).GetHeight() != 200; // see StyleSheetTable_Impl::StyleSheetTable_Impl() where we set this default
4996 break;
4997 case RES_CHRATR_KERNING:
4998 bMustWrite = rHt.StaticWhichCast(RES_CHRATR_KERNING).GetValue() != 0;
4999 break;
5000 case RES_CHRATR_LANGUAGE:
5001 bMustWrite = true;
5002 break;
5003 case RES_CHRATR_POSTURE:
5004 bMustWrite = rHt.StaticWhichCast(RES_CHRATR_POSTURE).GetPosture() != ITALIC_NONE;
5005 break;
5006 case RES_CHRATR_SHADOWED:
5007 bMustWrite = rHt.StaticWhichCast(RES_CHRATR_SHADOWED).GetValue();
5008 break;
5009 case RES_CHRATR_UNDERLINE:
5010 bMustWrite = rHt.StaticWhichCast(RES_CHRATR_UNDERLINE).GetLineStyle() != LINESTYLE_NONE;
5011 break;
5012 case RES_CHRATR_WEIGHT:
5013 bMustWrite = rHt.StaticWhichCast(RES_CHRATR_WEIGHT).GetWeight() != WEIGHT_NORMAL;
5014 break;
5015 case RES_CHRATR_AUTOKERN:
5016 bMustWrite = rHt.StaticWhichCast(RES_CHRATR_AUTOKERN).GetValue();
5017 break;
5018 case RES_CHRATR_BLINK:
5019 bMustWrite = rHt.StaticWhichCast(RES_CHRATR_BLINK).GetValue();
5020 break;
5021 case RES_CHRATR_BACKGROUND:
5023 const SvxBrushItem& rBrushItem = rHt.StaticWhichCast(RES_CHRATR_BACKGROUND);
5024 bMustWrite = (rBrushItem.GetColor() != COL_AUTO ||
5025 rBrushItem.GetShadingValue() != ShadingPattern::CLEAR ||
5026 rBrushItem.GetGraphicObject() != nullptr);
5028 break;
5030 case RES_CHRATR_CJK_FONT:
5031 bMustWrite = true;
5032 break;
5033 case RES_CHRATR_CJK_FONTSIZE:
5034 bMustWrite = false; // we have written it already as RES_CHRATR_FONTSIZE
5035 break;
5036 case RES_CHRATR_CJK_LANGUAGE:
5037 bMustWrite = true;
5038 break;
5039 case RES_CHRATR_CJK_POSTURE:
5040 bMustWrite = false; // we have written it already as RES_CHRATR_POSTURE
5041 break;
5042 case RES_CHRATR_CJK_WEIGHT:
5043 bMustWrite = false; // we have written it already as RES_CHRATR_WEIGHT
5044 break;
5046 case RES_CHRATR_CTL_FONT:
5047 bMustWrite = true;
5048 break;
5049 case RES_CHRATR_CTL_FONTSIZE:
5050 bMustWrite = rHt.StaticWhichCast(RES_CHRATR_CTL_FONTSIZE).GetHeight() != 200; // see StyleSheetTable_Impl::StyleSheetTable_Impl() where we set this default
5051 break;
5052 case RES_CHRATR_CTL_LANGUAGE:
5053 bMustWrite = true;
5054 break;
5055 case RES_CHRATR_CTL_POSTURE:
5056 bMustWrite = rHt.StaticWhichCast(RES_CHRATR_CTL_POSTURE).GetPosture() != ITALIC_NONE;
5057 break;
5058 case RES_CHRATR_CTL_WEIGHT:
5059 bMustWrite = rHt.StaticWhichCast(RES_CHRATR_CTL_WEIGHT).GetWeight() != WEIGHT_NORMAL;
5060 break;
5062 case RES_CHRATR_ROTATE:
5063 bMustWrite = rHt.StaticWhichCast(RES_CHRATR_ROTATE).GetValue() != 0_deg10;
5064 break;
5065 case RES_CHRATR_EMPHASIS_MARK:
5066 bMustWrite = rHt.StaticWhichCast(RES_CHRATR_EMPHASIS_MARK).GetEmphasisMark() != FontEmphasisMark::NONE;
5067 break;
5068 case RES_CHRATR_TWO_LINES:
5069 bMustWrite = rHt.StaticWhichCast(RES_CHRATR_TWO_LINES).GetValue();
5070 break;
5071 case RES_CHRATR_SCALEW:
5072 bMustWrite = rHt.StaticWhichCast(RES_CHRATR_SCALEW).GetValue() != 100;
5073 break;
5074 case RES_CHRATR_RELIEF:
5075 bMustWrite = rHt.StaticWhichCast(RES_CHRATR_RELIEF).GetValue() != FontRelief::NONE;
5076 break;
5077 case RES_CHRATR_HIDDEN:
5078 bMustWrite = rHt.StaticWhichCast(RES_CHRATR_HIDDEN).GetValue();
5079 break;
5080 case RES_CHRATR_BOX:
5082 const SvxBoxItem& rBoxItem = rHt.StaticWhichCast(RES_CHRATR_BOX);
5083 bMustWrite = rBoxItem.GetTop() || rBoxItem.GetLeft() ||
5084 rBoxItem.GetBottom() || rBoxItem.GetRight() ||
5085 rBoxItem.GetSmallestDistance();
5087 break;
5088 case RES_CHRATR_HIGHLIGHT:
5090 const SvxBrushItem& rBrushItem = rHt.StaticWhichCast(RES_CHRATR_HIGHLIGHT);
5091 bMustWrite = (rBrushItem.GetColor() != COL_AUTO ||
5092 rBrushItem.GetShadingValue() != ShadingPattern::CLEAR ||
5093 rBrushItem.GetGraphicObject() != nullptr);
5095 break;
5097 case RES_PARATR_LINESPACING:
5098 bMustWrite = rHt.StaticWhichCast(RES_PARATR_LINESPACING).GetInterLineSpaceRule() != SvxInterLineSpaceRule::Off;
5099 break;
5100 case RES_PARATR_ADJUST:
5101 bMustWrite = rHt.StaticWhichCast(RES_PARATR_ADJUST).GetAdjust() != SvxAdjust::Left;
5102 break;
5103 case RES_PARATR_SPLIT:
5104 bMustWrite = !rHt.StaticWhichCast(RES_PARATR_SPLIT).GetValue();
5105 break;
5106 case RES_PARATR_WIDOWS:
5107 bMustWrite = rHt.StaticWhichCast(RES_PARATR_WIDOWS).GetValue();
5108 break;
5109 case RES_PARATR_TABSTOP:
5110 bMustWrite = rHt.StaticWhichCast(RES_PARATR_TABSTOP).Count() != 0;
5111 break;
5112 case RES_PARATR_HYPHENZONE:
5113 bMustWrite = true;
5114 break;
5115 case RES_PARATR_NUMRULE:
5116 bMustWrite = !rHt.StaticWhichCast(RES_PARATR_NUMRULE).GetValue().isEmpty();
5117 break;
5118 case RES_PARATR_SCRIPTSPACE:
5119 bMustWrite = !static_cast< const SfxBoolItem& >(rHt).GetValue();
5120 break;
5121 case RES_PARATR_HANGINGPUNCTUATION:
5122 bMustWrite = !static_cast< const SfxBoolItem& >(rHt).GetValue();
5123 break;
5124 case RES_PARATR_FORBIDDEN_RULES:
5125 bMustWrite = !static_cast< const SfxBoolItem& >(rHt).GetValue();
5126 break;
5127 case RES_PARATR_VERTALIGN:
5128 bMustWrite = rHt.StaticWhichCast(RES_PARATR_VERTALIGN).GetValue() != SvxParaVertAlignItem::Align::Automatic;
5129 break;
5130 case RES_PARATR_SNAPTOGRID:
5131 bMustWrite = !rHt.StaticWhichCast(RES_PARATR_SNAPTOGRID).GetValue();
5132 break;
5133 case RES_CHRATR_GRABBAG:
5134 bMustWrite = true;
5135 break;
5137 default:
5138 SAL_INFO("sw.ww8", "Unhandled SfxPoolItem with id " << rHt.Which() );
5139 break;
5142 if (bMustWrite)
5143 OutputItem(rHt);
5146 void DocxAttributeOutput::DocDefaults( )
5148 // Write the '<w:docDefaults>' section here
5149 m_pSerializer->startElementNS(XML_w, XML_docDefaults);
5151 // Output the default run properties
5152 m_pSerializer->startElementNS(XML_w, XML_rPrDefault);
5154 StartStyleProperties(false, 0);
5156 for (int i = int(RES_CHRATR_BEGIN); i < int(RES_CHRATR_END); ++i)
5157 OutputDefaultItem(m_rExport.m_rDoc.GetDefault(i));
5159 EndStyleProperties(false);
5161 m_pSerializer->endElementNS(XML_w, XML_rPrDefault);
5163 // Output the default paragraph properties
5164 m_pSerializer->startElementNS(XML_w, XML_pPrDefault);
5166 StartStyleProperties(true, 0);
5168 for (int i = int(RES_PARATR_BEGIN); i < int(RES_PARATR_END); ++i)
5169 OutputDefaultItem(m_rExport.m_rDoc.GetDefault(i));
5171 EndStyleProperties(true);
5173 m_pSerializer->endElementNS(XML_w, XML_pPrDefault);
5175 m_pSerializer->endElementNS(XML_w, XML_docDefaults);
5178 void DocxAttributeOutput::EndStyles( sal_uInt16 nNumberOfStyles )
5180 // HACK
5181 // Ms Office seems to have an internal limitation of 4091 styles
5182 // and refuses to load .docx with more, even though the spec seems to allow that;
5183 // so simply if there are more styles, don't export those
5184 const sal_Int32 nCountStylesToWrite = MSWORD_MAX_STYLES_LIMIT - nNumberOfStyles;
5185 m_pTableStyleExport->TableStyles(nCountStylesToWrite);
5186 m_pSerializer->endElementNS( XML_w, XML_styles );
5189 void DocxAttributeOutput::DefaultStyle()
5191 // are these the values of enum ww::sti (see ../inc/wwstyles.hxx)?
5192 SAL_INFO("sw.ww8", "TODO DocxAttributeOutput::DefaultStyle()");
5195 /* Writes <a:srcRect> tag back to document.xml if a file contains a cropped image.
5196 * NOTE : Tested on images of type JPEG,EMF/WMF,BMP, PNG and GIF.
5198 void DocxAttributeOutput::WriteSrcRect(
5199 const css::uno::Reference<css::beans::XPropertySet>& xShapePropSet,
5200 const SwFrameFormat* pFrameFormat)
5202 uno::Reference<graphic::XGraphic> xGraphic;
5203 xShapePropSet->getPropertyValue(u"Graphic"_ustr) >>= xGraphic;
5204 const Graphic aGraphic(xGraphic);
5206 Size aOriginalSize(aGraphic.GetPrefSize());
5208 const MapMode aMap100mm( MapUnit::Map100thMM );
5209 const MapMode aMapMode = aGraphic.GetPrefMapMode();
5210 if (aMapMode.GetMapUnit() == MapUnit::MapPixel)
5212 aOriginalSize = Application::GetDefaultDevice()->PixelToLogic(aOriginalSize, aMap100mm);
5215 css::text::GraphicCrop aGraphicCropStruct;
5216 xShapePropSet->getPropertyValue(u"GraphicCrop"_ustr) >>= aGraphicCropStruct;
5217 sal_Int32 nCropL = aGraphicCropStruct.Left;
5218 sal_Int32 nCropR = aGraphicCropStruct.Right;
5219 sal_Int32 nCropT = aGraphicCropStruct.Top;
5220 sal_Int32 nCropB = aGraphicCropStruct.Bottom;
5222 // simulate border padding as a negative crop.
5223 const SvxBoxItem* pBoxItem;
5224 if (pFrameFormat && (pBoxItem = pFrameFormat->GetItemIfSet(RES_BOX, false)))
5226 nCropL -= pBoxItem->GetDistance( SvxBoxItemLine::LEFT );
5227 nCropR -= pBoxItem->GetDistance( SvxBoxItemLine::RIGHT );
5228 nCropT -= pBoxItem->GetDistance( SvxBoxItemLine::TOP );
5229 nCropB -= pBoxItem->GetDistance( SvxBoxItemLine::BOTTOM );
5232 if (nCropL == 0 && nCropT == 0 && nCropR == 0 && nCropB == 0)
5233 return;
5235 double widthMultiplier = 100000.0/aOriginalSize.Width();
5236 double heightMultiplier = 100000.0/aOriginalSize.Height();
5238 sal_Int32 left = static_cast<sal_Int32>(rtl::math::round(nCropL * widthMultiplier));
5239 sal_Int32 right = static_cast<sal_Int32>(rtl::math::round(nCropR * widthMultiplier));
5240 sal_Int32 top = static_cast<sal_Int32>(rtl::math::round(nCropT * heightMultiplier));
5241 sal_Int32 bottom = static_cast<sal_Int32>(rtl::math::round(nCropB * heightMultiplier));
5243 m_pSerializer->singleElementNS( XML_a, XML_srcRect,
5244 XML_l, OString::number(left),
5245 XML_t, OString::number(top),
5246 XML_r, OString::number(right),
5247 XML_b, OString::number(bottom) );
5250 uno::Reference<css::text::XTextFrame> DocxAttributeOutput::GetUnoTextFrame(
5251 css::uno::Reference<css::drawing::XShape> xShape)
5253 return SwTextBoxHelper::getUnoTextFrame(xShape);
5256 static rtl::Reference<::sax_fastparser::FastAttributeList> CreateDocPrAttrList(
5257 DocxExport & rExport, int const nAnchorId, std::u16string_view const& rName,
5258 std::u16string_view const& rTitle, std::u16string_view const& rDescription)
5260 rtl::Reference<::sax_fastparser::FastAttributeList> const pAttrs(FastSerializerHelper::createAttrList());
5261 pAttrs->add(XML_id, OString::number(nAnchorId));
5262 pAttrs->add(XML_name, rName);
5263 if (rExport.GetFilter().getVersion() != oox::core::ECMA_376_1ST_EDITION)
5265 pAttrs->add(XML_descr, rDescription);
5266 pAttrs->add(XML_title, rTitle);
5268 else
5269 { // tdf#148952 no title attribute, merge it into descr
5270 OUString const value(rTitle.empty()
5271 ? OUString(rDescription)
5272 : rDescription.empty()
5273 ? OUString(rTitle)
5274 : OUString::Concat(rTitle) + "\n" + rDescription);
5275 pAttrs->add(XML_descr, value);
5277 return pAttrs;
5280 void DocxAttributeOutput::FlyFrameGraphic( const SwGrfNode* pGrfNode, const Size& rSize, const SwFlyFrameFormat* pOLEFrameFormat, SwOLENode* pOLENode, const SdrObject* pSdrObj )
5282 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" );
5284 GetSdtEndBefore(pSdrObj);
5286 // detect mis-use of the API
5287 assert(pGrfNode || (pOLEFrameFormat && pOLENode));
5288 const SwFrameFormat* pFrameFormat = pGrfNode ? pGrfNode->GetFlyFormat() : pOLEFrameFormat;
5289 // create the relation ID
5290 OString aRelId;
5291 OUString sSvgRelId;
5292 sal_Int32 nImageType;
5293 if ( pGrfNode && pGrfNode->IsLinkedFile() )
5295 // linked image, just create the relation
5296 OUString aFileName;
5297 pGrfNode->GetFileFilterNms( &aFileName, nullptr );
5299 sal_Int32 const nFragment(aFileName.indexOf('#'));
5300 sal_Int32 const nForbiddenU(aFileName.indexOf("%5C"));
5301 sal_Int32 const nForbiddenL(aFileName.indexOf("%5c"));
5302 if ( (nForbiddenU != -1 && (nFragment == -1 || nForbiddenU < nFragment))
5303 || (nForbiddenL != -1 && (nFragment == -1 || nForbiddenL < nFragment)))
5305 SAL_WARN("sw.ww8", "DocxAttributeOutput::FlyFrameGraphic: ignoring image with invalid link URL");
5306 return;
5309 // TODO Convert the file name to relative for better interoperability
5311 aRelId = m_rExport.AddRelation(
5312 oox::getRelationship(Relationship::IMAGE),
5313 aFileName );
5315 nImageType = XML_link;
5317 else
5319 // inline, we also have to write the image itself
5320 Graphic aGraphic;
5321 if (pGrfNode)
5322 aGraphic = pGrfNode->GetGrf();
5323 else if (const Graphic* pGraphic = pOLENode->GetGraphic())
5324 aGraphic = *pGraphic;
5326 m_rDrawingML.SetFS(m_pSerializer); // to be sure that we write to the right stream
5327 auto pGraphicExport = m_rDrawingML.createGraphicExport();
5328 OUString aImageId = pGraphicExport->writeToStorage(aGraphic, false);
5329 aRelId = OUStringToOString(aImageId, RTL_TEXTENCODING_UTF8);
5331 if (aGraphic.getVectorGraphicData() && aGraphic.getVectorGraphicData()->getType() == VectorGraphicDataType::Svg)
5333 sSvgRelId = pGraphicExport->writeToStorage(aGraphic, false, drawingml::GraphicExport::TypeHint::SVG);
5335 nImageType = XML_embed;
5338 // In case there are any grab-bag items on the graphic frame, emit them now.
5339 // These are always character grab-bags, as graphics are at-char or as-char in Word.
5340 if (const SfxGrabBagItem* pGrabBag = pFrameFormat->GetAttrSet().GetItemIfSet(RES_FRMATR_GRABBAG))
5342 CharGrabBag(*pGrabBag);
5345 rtl::Reference<sax_fastparser::FastAttributeList> xFrameAttributes(
5346 FastSerializerHelper::createAttrList());
5347 if (pGrfNode)
5349 const SwAttrSet& rSet = pGrfNode->GetSwAttrSet();
5350 MirrorGraph eMirror = rSet.Get(RES_GRFATR_MIRRORGRF).GetValue();
5351 if (eMirror == MirrorGraph::Vertical || eMirror == MirrorGraph::Both)
5352 // Mirror on the vertical axis is a horizontal flip.
5353 xFrameAttributes->add(XML_flipH, "1");
5354 // RES_GRFATR_ROTATION is sal_uInt16; use sal_uInt32 for multiplication later
5355 if (Degree10 nRot = rSet.Get(RES_GRFATR_ROTATION).GetValue())
5357 // RES_GRFATR_ROTATION is in 10ths of degree; convert to 100ths for macro
5358 sal_uInt32 mOOXMLRot = oox::drawingml::ExportRotateClockwisify(to<Degree100>(nRot));
5359 xFrameAttributes->add(XML_rot, OString::number(mOOXMLRot));
5363 css::uno::Reference<css::beans::XPropertySet> xShapePropSet;
5364 if (pSdrObj)
5366 css::uno::Reference<css::drawing::XShape> xShape(
5367 const_cast<SdrObject*>(pSdrObj)->getUnoShape(), css::uno::UNO_QUERY);
5368 xShapePropSet.set(xShape, css::uno::UNO_QUERY);
5369 assert(xShapePropSet);
5372 Size aSize = rSize;
5373 // We need the original (cropped, but unrotated) size of object. So prefer the object data,
5374 // and only use passed frame size as fallback.
5375 if (xShapePropSet)
5377 if (css::awt::Size val; xShapePropSet->getPropertyValue(u"Size"_ustr) >>= val)
5378 aSize = Size(o3tl::toTwips(val.Width, o3tl::Length::mm100), o3tl::toTwips(val.Height, o3tl::Length::mm100));
5381 m_rExport.SdrExporter().startDMLAnchorInline(pFrameFormat, aSize);
5383 // picture description (used for pic:cNvPr later too)
5384 OUString const descr(pGrfNode ? pGrfNode->GetDescription() : pOLEFrameFormat->GetObjDescription());
5385 OUString const title(pGrfNode ? pGrfNode->GetTitle() : pOLEFrameFormat->GetObjTitle());
5386 auto const docPrattrList(CreateDocPrAttrList(
5387 GetExport(), m_anchorId++, pFrameFormat->GetName(), title, descr));
5388 m_pSerializer->startElementNS( XML_wp, XML_docPr, docPrattrList );
5390 OUString sURL, sRelId;
5391 if (xShapePropSet)
5393 xShapePropSet->getPropertyValue(u"HyperLinkURL"_ustr) >>= sURL;
5394 if(!sURL.isEmpty())
5396 if (sURL.startsWith("#") && sURL.indexOf(' ') != -1 && !sURL.endsWith("|outline") && !sURL.endsWith("|table") &&
5397 !sURL.endsWith("|frame") && !sURL.endsWith("|graphic") && !sURL.endsWith("|ole") && !sURL.endsWith("|region"))
5399 // Spaces are prohibited in bookmark name.
5400 sURL = sURL.replace(' ', '_');
5402 sRelId = GetExport().GetFilter().addRelation( m_pSerializer->getOutputStream(),
5403 oox::getRelationship(Relationship::HYPERLINK),
5404 sURL, !sURL.startsWith("#") );
5405 m_pSerializer->singleElementNS( XML_a, XML_hlinkClick,
5406 FSNS( XML_xmlns, XML_a ), "http://schemas.openxmlformats.org/drawingml/2006/main",
5407 FSNS( XML_r, XML_id ), sRelId);
5409 AddExtLst(m_pSerializer, GetExport(), xShapePropSet);
5412 m_pSerializer->endElementNS( XML_wp, XML_docPr );
5414 m_pSerializer->startElementNS(XML_wp, XML_cNvGraphicFramePr);
5415 // TODO change aspect?
5416 m_pSerializer->singleElementNS( XML_a, XML_graphicFrameLocks,
5417 FSNS( XML_xmlns, XML_a ), GetExport().GetFilter().getNamespaceURL(OOX_NS(dml)),
5418 XML_noChangeAspect, "1" );
5419 m_pSerializer->endElementNS( XML_wp, XML_cNvGraphicFramePr );
5421 m_pSerializer->startElementNS( XML_a, XML_graphic,
5422 FSNS( XML_xmlns, XML_a ), GetExport().GetFilter().getNamespaceURL(OOX_NS(dml)) );
5423 m_pSerializer->startElementNS( XML_a, XML_graphicData,
5424 XML_uri, "http://schemas.openxmlformats.org/drawingml/2006/picture" );
5426 m_pSerializer->startElementNS( XML_pic, XML_pic,
5427 FSNS( XML_xmlns, XML_pic ), GetExport().GetFilter().getNamespaceURL(OOX_NS(dmlPicture)) );
5429 m_pSerializer->startElementNS(XML_pic, XML_nvPicPr);
5430 // It seems pic:cNvpr and wp:docPr are pretty much the same thing with the same attributes
5431 m_pSerializer->startElementNS(XML_pic, XML_cNvPr, docPrattrList);
5433 if(!sURL.isEmpty())
5434 m_pSerializer->singleElementNS(XML_a, XML_hlinkClick, FSNS(XML_r, XML_id), sRelId);
5436 m_pSerializer->endElementNS( XML_pic, XML_cNvPr );
5438 m_pSerializer->startElementNS(XML_pic, XML_cNvPicPr);
5439 // TODO change aspect?
5440 m_pSerializer->singleElementNS( XML_a, XML_picLocks,
5441 XML_noChangeAspect, "1", XML_noChangeArrowheads, "1" );
5442 m_pSerializer->endElementNS( XML_pic, XML_cNvPicPr );
5443 m_pSerializer->endElementNS( XML_pic, XML_nvPicPr );
5445 // the actual picture
5446 m_pSerializer->startElementNS(XML_pic, XML_blipFill);
5448 /* At this point we are certain that, WriteImage returns empty RelId
5449 for unhandled graphic type. Therefore we write the picture description
5450 and not the relation( coz there ain't any), so that the user knows
5451 there is an image/graphic in the doc but it is broken instead of
5452 completely discarding it.
5454 if ( aRelId.isEmpty() )
5455 m_pSerializer->startElementNS(XML_a, XML_blip);
5456 else
5457 m_pSerializer->startElementNS(XML_a, XML_blip, FSNS(XML_r, nImageType), aRelId);
5459 const SfxEnumItemInterface* pGrafModeItem = nullptr;
5460 if ( pGrfNode && (pGrafModeItem = pGrfNode->GetSwAttrSet().GetItemIfSet(RES_GRFATR_DRAWMODE)))
5462 GraphicDrawMode nMode = static_cast<GraphicDrawMode>(pGrafModeItem->GetEnumValue());
5463 if (nMode == GraphicDrawMode::Greys)
5464 m_pSerializer->singleElementNS (XML_a, XML_grayscl);
5465 else if (nMode == GraphicDrawMode::Mono) //black/white has a 0,5 threshold in LibreOffice
5466 m_pSerializer->singleElementNS (XML_a, XML_biLevel, XML_thresh, OString::number(50000));
5467 else if (nMode == GraphicDrawMode::Watermark) //watermark has a brightness/luminance of 0,5 and contrast of -0.7 in LibreOffice
5468 m_pSerializer->singleElementNS( XML_a, XML_lum, XML_bright, OString::number(70000), XML_contrast, OString::number(-70000) );
5471 if (!sSvgRelId.isEmpty())
5473 auto pGraphicExport = m_rDrawingML.createGraphicExport();
5474 pGraphicExport->writeSvgExtension(sSvgRelId);
5477 m_pSerializer->endElementNS( XML_a, XML_blip );
5479 if (xShapePropSet)
5480 WriteSrcRect(xShapePropSet, pFrameFormat);
5482 m_pSerializer->startElementNS(XML_a, XML_stretch);
5483 m_pSerializer->singleElementNS(XML_a, XML_fillRect);
5484 m_pSerializer->endElementNS( XML_a, XML_stretch );
5485 m_pSerializer->endElementNS( XML_pic, XML_blipFill );
5487 // TODO setup the right values below
5488 m_pSerializer->startElementNS(XML_pic, XML_spPr, XML_bwMode, "auto");
5490 m_pSerializer->startElementNS(XML_a, XML_xfrm, xFrameAttributes);
5492 m_pSerializer->singleElementNS(XML_a, XML_off, XML_x, "0", XML_y, "0");
5493 OString aWidth( OString::number( TwipsToEMU( aSize.Width() ) ) );
5494 OString aHeight( OString::number( TwipsToEMU( aSize.Height() ) ) );
5495 m_pSerializer->singleElementNS(XML_a, XML_ext, XML_cx, aWidth, XML_cy, aHeight);
5496 m_pSerializer->endElementNS( XML_a, XML_xfrm );
5497 m_pSerializer->startElementNS(XML_a, XML_prstGeom, XML_prst, "rect");
5498 m_pSerializer->singleElementNS(XML_a, XML_avLst);
5499 m_pSerializer->endElementNS( XML_a, XML_prstGeom );
5501 m_rDrawingML.SetFS(m_pSerializer); // to be sure that we write to the right stream
5502 if (xShapePropSet)
5503 m_rDrawingML.WriteFill(xShapePropSet, awt::Size(aSize.Width(), aSize.Height()));
5505 const SvxBoxItem& rBoxItem = pFrameFormat->GetBox();
5506 const SvxBorderLine* pLeft = rBoxItem.GetLine(SvxBoxItemLine::LEFT);
5507 const SvxBorderLine* pRight = rBoxItem.GetLine(SvxBoxItemLine::RIGHT);
5508 const SvxBorderLine* pTop = rBoxItem.GetLine(SvxBoxItemLine::TOP);
5509 const SvxBorderLine* pBottom = rBoxItem.GetLine(SvxBoxItemLine::BOTTOM);
5510 if (pLeft || pRight || pTop || pBottom)
5511 m_rExport.SdrExporter().writeBoxItemLine(rBoxItem);
5513 m_rExport.SdrExporter().writeDMLEffectLst(*pFrameFormat);
5515 m_pSerializer->endElementNS( XML_pic, XML_spPr );
5517 m_pSerializer->endElementNS( XML_pic, XML_pic );
5519 m_pSerializer->endElementNS( XML_a, XML_graphicData );
5520 m_pSerializer->endElementNS( XML_a, XML_graphic );
5521 m_rExport.SdrExporter().endDMLAnchorInline(pFrameFormat);
5524 void DocxAttributeOutput::WriteOLE2Obj( const SdrObject* pSdrObj, SwOLENode& rOLENode, const Size& rSize, const SwFlyFrameFormat* pFlyFrameFormat, const sal_Int8 nFormulaAlignment )
5526 if( WriteOLEChart( pSdrObj, rSize, pFlyFrameFormat ))
5527 return;
5528 if( WriteOLEMath( rOLENode , nFormulaAlignment))
5529 return;
5530 PostponeOLE( rOLENode, rSize, pFlyFrameFormat );
5533 bool DocxAttributeOutput::WriteOLEChart( const SdrObject* pSdrObj, const Size& rSize, const SwFlyFrameFormat* pFlyFrameFormat )
5535 uno::Reference< drawing::XShape > xShape( const_cast<SdrObject*>(pSdrObj)->getUnoShape(), uno::UNO_QUERY );
5536 if (!xShape.is())
5537 return false;
5539 uno::Reference<beans::XPropertySet> const xPropSet(xShape, uno::UNO_QUERY);
5540 if (!xPropSet.is())
5541 return false;
5543 OUString clsid; // why is the property of type string, not sequence<byte>?
5544 xPropSet->getPropertyValue(u"CLSID"_ustr) >>= clsid;
5545 assert(!clsid.isEmpty());
5546 SvGlobalName aClassID;
5547 bool const isValid(aClassID.MakeId(clsid));
5548 assert(isValid); (void)isValid;
5550 if (!SotExchange::IsChart(aClassID))
5551 return false;
5553 m_aPostponedCharts.push_back(PostponedChart(pSdrObj, rSize, pFlyFrameFormat));
5554 return true;
5558 * Write chart hierarchy in w:drawing after end element of w:rPr tag.
5560 void DocxAttributeOutput::WritePostponedChart()
5562 if (m_aPostponedCharts.empty())
5563 return;
5565 for (const PostponedChart& rChart : m_aPostponedCharts)
5567 uno::Reference< chart2::XChartDocument > xChartDoc;
5568 uno::Reference< drawing::XShape > xShape(const_cast<SdrObject*>(rChart.object)->getUnoShape(), uno::UNO_QUERY );
5569 if( xShape.is() )
5571 uno::Reference< beans::XPropertySet > xPropSet( xShape, uno::UNO_QUERY );
5572 if( xPropSet.is() )
5573 xChartDoc.set( xPropSet->getPropertyValue( u"Model"_ustr ), uno::UNO_QUERY );
5576 if( xChartDoc.is() )
5578 SAL_INFO("sw.ww8", "DocxAttributeOutput::WriteOLE2Obj: export chart ");
5580 m_rExport.SdrExporter().startDMLAnchorInline(rChart.frame, rChart.size);
5582 OUString sName(u"Object 1"_ustr);
5583 uno::Reference< container::XNamed > xNamed( xShape, uno::UNO_QUERY );
5584 if( xNamed.is() )
5585 sName = xNamed->getName();
5587 // tdf#153203 export a11y related properties
5588 uno::Reference<beans::XPropertySet> xShapeProps(xShape, uno::UNO_QUERY);
5589 OUString const title(xShapeProps->getPropertyValue(u"Title"_ustr).get<OUString>());
5590 OUString const descr(xShapeProps->getPropertyValue(u"Description"_ustr).get<OUString>());
5592 /* If there is a scenario where a chart is followed by a shape
5593 which is being exported as an alternate content then, the
5594 docPr Id is being repeated, ECMA 20.4.2.5 says that the
5595 docPr Id should be unique, ensuring the same here.
5597 auto const docPrattrList(CreateDocPrAttrList(
5598 GetExport(), m_anchorId++, sName, title, descr));
5599 m_pSerializer->singleElementNS(XML_wp, XML_docPr, docPrattrList);
5601 m_pSerializer->singleElementNS(XML_wp, XML_cNvGraphicFramePr);
5603 m_pSerializer->startElementNS( XML_a, XML_graphic,
5604 FSNS( XML_xmlns, XML_a ), GetExport().GetFilter().getNamespaceURL(OOX_NS(dml)) );
5606 m_pSerializer->startElementNS( XML_a, XML_graphicData,
5607 XML_uri, "http://schemas.openxmlformats.org/drawingml/2006/chart" );
5609 OString aRelId;
5610 m_nChartCount++;
5611 aRelId = m_rExport.OutputChart( xChartDoc, m_nChartCount, m_pSerializer );
5613 m_pSerializer->singleElementNS( XML_c, XML_chart,
5614 FSNS( XML_xmlns, XML_c ), GetExport().GetFilter().getNamespaceURL(OOX_NS(dmlChart)),
5615 FSNS( XML_xmlns, XML_r ), GetExport().GetFilter().getNamespaceURL(OOX_NS(officeRel)),
5616 FSNS( XML_r, XML_id ), aRelId );
5618 m_pSerializer->endElementNS( XML_a, XML_graphicData );
5619 m_pSerializer->endElementNS( XML_a, XML_graphic );
5621 m_rExport.SdrExporter().endDMLAnchorInline(rChart.frame);
5625 m_aPostponedCharts.clear();
5628 bool DocxAttributeOutput::WriteOLEMath( const SwOLENode& rOLENode ,const sal_Int8 nAlign)
5630 uno::Reference < embed::XEmbeddedObject > xObj(const_cast<SwOLENode&>(rOLENode).GetOLEObj().GetOleRef());
5631 SvGlobalName aObjName(xObj->getClassID());
5633 if( !SotExchange::IsMath(aObjName) )
5634 return false;
5638 PostponedMathObjects aPostponedMathObject;
5639 aPostponedMathObject.pMathObject = const_cast<SwOLENode*>( &rOLENode);
5640 aPostponedMathObject.nMathObjAlignment = nAlign;
5641 m_aPostponedMaths.push_back(aPostponedMathObject);
5643 catch (const uno::Exception&)
5646 return true;
5649 void DocxAttributeOutput::WritePostponedMath(const SwOLENode* pPostponedMath, sal_Int8 nAlign)
5651 uno::Reference < embed::XEmbeddedObject > xObj(const_cast<SwOLENode*>(pPostponedMath)->GetOLEObj().GetOleRef());
5652 if (embed::EmbedStates::LOADED == xObj->getCurrentState())
5654 // must be running so there is a Component
5657 xObj->changeState(embed::EmbedStates::RUNNING);
5659 catch (const uno::Exception&)
5663 uno::Reference< uno::XInterface > xInterface( xObj->getComponent(), uno::UNO_QUERY );
5664 if( oox::FormulaImExportBase* formulaexport = dynamic_cast< oox::FormulaImExportBase* >( xInterface.get()))
5665 formulaexport->writeFormulaOoxml( m_pSerializer, GetExport().GetFilter().getVersion(),
5666 oox::drawingml::DOCUMENT_DOCX, nAlign);
5667 else
5668 OSL_FAIL( "Math OLE object cannot write out OOXML" );
5671 void DocxAttributeOutput::WritePostponedFormControl(const SdrObject* pObject)
5673 if (!pObject || pObject->GetObjInventor() != SdrInventor::FmForm)
5674 return;
5676 SdrUnoObj *pFormObj = const_cast<SdrUnoObj*>(dynamic_cast< const SdrUnoObj*>(pObject));
5677 if (!pFormObj)
5678 return;
5680 uno::Reference<awt::XControlModel> xControlModel = pFormObj->GetUnoControlModel();
5681 uno::Reference<lang::XServiceInfo> xInfo(xControlModel, uno::UNO_QUERY);
5682 if (!xInfo.is())
5683 return;
5685 if (xInfo->supportsService(u"com.sun.star.form.component.DateField"_ustr))
5687 // gather component properties
5689 OUString sDateFormat;
5690 uno::Reference<beans::XPropertySet> xPropertySet(xControlModel, uno::UNO_QUERY);
5692 OString sDate;
5693 OUString aContentText;
5694 bool bHasDate = false;
5695 css::util::Date aUNODate;
5696 if (xPropertySet->getPropertyValue(u"Date"_ustr) >>= aUNODate)
5698 bHasDate = true;
5699 Date aDate(aUNODate.Day, aUNODate.Month, aUNODate.Year);
5700 sDate = DateToOString(aDate);
5701 aContentText = DateToDDMMYYYYOUString(aDate);
5702 sDateFormat = "dd/MM/yyyy";
5704 else
5706 aContentText = xPropertySet->getPropertyValue(u"HelpText"_ustr).get<OUString>();
5707 if(sDateFormat.isEmpty())
5708 sDateFormat = "dd/MM/yyyy"; // Need to set date format even if there is no date set
5711 // output component
5713 m_pSerializer->startElementNS(XML_w, XML_sdt);
5714 m_pSerializer->startElementNS(XML_w, XML_sdtPr);
5716 if (bHasDate)
5717 m_pSerializer->startElementNS(XML_w, XML_date, FSNS(XML_w, XML_fullDate), sDate);
5718 else
5719 m_pSerializer->startElementNS(XML_w, XML_date);
5721 m_pSerializer->singleElementNS(XML_w, XML_dateFormat, FSNS(XML_w, XML_val), sDateFormat);
5722 m_pSerializer->singleElementNS(XML_w, XML_lid,
5723 FSNS(XML_w, XML_val), "en-US");
5724 m_pSerializer->singleElementNS(XML_w, XML_storeMappedDataAs,
5725 FSNS(XML_w, XML_val), "dateTime");
5726 m_pSerializer->singleElementNS(XML_w, XML_calendar,
5727 FSNS(XML_w, XML_val), "gregorian");
5729 m_pSerializer->endElementNS(XML_w, XML_date);
5730 m_pSerializer->endElementNS(XML_w, XML_sdtPr);
5732 m_pSerializer->startElementNS(XML_w, XML_sdtContent);
5733 m_pSerializer->startElementNS(XML_w, XML_r);
5735 RunText(aContentText);
5736 m_pSerializer->endElementNS(XML_w, XML_r);
5737 m_pSerializer->endElementNS(XML_w, XML_sdtContent);
5739 m_pSerializer->endElementNS(XML_w, XML_sdt);
5741 else if (xInfo->supportsService(u"com.sun.star.form.component.ComboBox"_ustr))
5743 // gather component properties
5745 uno::Reference<beans::XPropertySet> xPropertySet(xControlModel, uno::UNO_QUERY);
5746 OUString sText = xPropertySet->getPropertyValue(u"Text"_ustr).get<OUString>();
5747 const uno::Sequence<OUString> aItems = xPropertySet->getPropertyValue(u"StringItemList"_ustr).get< uno::Sequence<OUString> >();
5749 // output component
5751 m_pSerializer->startElementNS(XML_w, XML_sdt);
5752 m_pSerializer->startElementNS(XML_w, XML_sdtPr);
5754 m_pSerializer->startElementNS(XML_w, XML_dropDownList);
5756 for (const auto& rItem : aItems)
5758 m_pSerializer->singleElementNS(XML_w, XML_listItem,
5759 FSNS(XML_w, XML_displayText), rItem,
5760 FSNS(XML_w, XML_value), rItem);
5763 m_pSerializer->endElementNS(XML_w, XML_dropDownList);
5764 m_pSerializer->endElementNS(XML_w, XML_sdtPr);
5766 m_pSerializer->startElementNS(XML_w, XML_sdtContent);
5767 m_pSerializer->startElementNS(XML_w, XML_r);
5768 RunText(sText);
5769 m_pSerializer->endElementNS(XML_w, XML_r);
5770 m_pSerializer->endElementNS(XML_w, XML_sdtContent);
5772 m_pSerializer->endElementNS(XML_w, XML_sdt);
5776 void DocxAttributeOutput::WritePostponedActiveXControl(bool bInsideRun)
5778 for( const auto & rPostponedDrawing : m_aPostponedActiveXControls )
5780 WriteActiveXControl(rPostponedDrawing.object, *rPostponedDrawing.frame, bInsideRun);
5782 m_aPostponedActiveXControls.clear();
5786 void DocxAttributeOutput::WriteActiveXControl(const SdrObject* pObject, const SwFrameFormat& rFrameFormat, bool bInsideRun)
5788 SdrUnoObj *pFormObj = const_cast<SdrUnoObj*>(dynamic_cast< const SdrUnoObj*>(pObject));
5789 if (!pFormObj)
5790 return;
5792 uno::Reference<awt::XControlModel> xControlModel = pFormObj->GetUnoControlModel();
5793 if (!xControlModel.is())
5794 return;
5796 const bool bAnchoredInline = rFrameFormat.GetAnchor().GetAnchorId() == static_cast<RndStdIds>(css::text::TextContentAnchorType_AS_CHARACTER);
5798 if(!bInsideRun)
5800 m_pSerializer->startElementNS(XML_w, XML_r);
5803 // w:pict for floating embedded control and w:object for inline embedded control
5804 if(bAnchoredInline)
5805 m_pSerializer->startElementNS(XML_w, XML_object);
5806 else
5807 m_pSerializer->startElementNS(XML_w, XML_pict);
5809 // write ActiveX fragment and ActiveX binary
5810 uno::Reference<drawing::XShape> xShape(const_cast<SdrObject*>(pObject)->getUnoShape(), uno::UNO_QUERY);
5811 std::pair<OString,OString> sRelIdAndName = m_rExport.WriteActiveXObject(xShape, xControlModel);
5813 // VML shape definition
5814 m_rExport.VMLExporter().SetSkipwzName(true);
5815 m_rExport.VMLExporter().SetHashMarkForType(true);
5816 m_rExport.VMLExporter().OverrideShapeIDGen(true, "control_shape_"_ostr);
5817 OString sShapeId;
5818 if(bAnchoredInline)
5820 sShapeId = m_rExport.VMLExporter().AddInlineSdrObject(*pObject, true);
5822 else
5824 SwFormatFollowTextFlow const& rFlow(rFrameFormat.GetFollowTextFlow());
5825 const SwFormatHoriOrient& rHoriOri = rFrameFormat.GetHoriOrient();
5826 const SwFormatVertOrient& rVertOri = rFrameFormat.GetVertOrient();
5827 SwFormatSurround const& rSurround(rFrameFormat.GetSurround());
5828 rtl::Reference<sax_fastparser::FastAttributeList> pAttrList(docx::SurroundToVMLWrap(rSurround));
5829 sShapeId = m_rExport.VMLExporter().AddSdrObject(*pObject,
5830 rFlow.GetValue(),
5831 rHoriOri.GetHoriOrient(), rVertOri.GetVertOrient(),
5832 rHoriOri.GetRelationOrient(),
5833 rVertOri.GetRelationOrient(),
5834 pAttrList.get(),
5835 true);
5837 // Restore default values
5838 m_rExport.VMLExporter().SetSkipwzName(false);
5839 m_rExport.VMLExporter().SetHashMarkForType(false);
5840 m_rExport.VMLExporter().OverrideShapeIDGen(false);
5842 // control
5843 m_pSerializer->singleElementNS(XML_w, XML_control,
5844 FSNS(XML_r, XML_id), sRelIdAndName.first,
5845 FSNS(XML_w, XML_name), sRelIdAndName.second,
5846 FSNS(XML_w, XML_shapeid), sShapeId);
5848 if(bAnchoredInline)
5849 m_pSerializer->endElementNS(XML_w, XML_object);
5850 else
5851 m_pSerializer->endElementNS(XML_w, XML_pict);
5853 if(!bInsideRun)
5855 m_pSerializer->endElementNS(XML_w, XML_r);
5859 bool DocxAttributeOutput::ExportAsActiveXControl(const SdrObject* pObject) const
5861 SdrUnoObj *pFormObj = const_cast<SdrUnoObj*>(dynamic_cast< const SdrUnoObj*>(pObject));
5862 if (!pFormObj)
5863 return false;
5865 uno::Reference<awt::XControlModel> xControlModel = pFormObj->GetUnoControlModel();
5866 if (!xControlModel.is())
5867 return false;
5869 SwDocShell* pShell = m_rExport.m_rDoc.GetDocShell();
5870 uno::Reference< css::frame::XModel > xModel( pShell ? pShell->GetModel() : nullptr );
5871 if (!xModel.is())
5872 return false;
5874 uno::Reference<lang::XServiceInfo> xInfo(xControlModel, uno::UNO_QUERY);
5875 if (!xInfo.is())
5876 return false;
5878 // See WritePostponedFormControl
5879 // By now date field and combobox is handled on a different way, so let's not interfere with the other method.
5880 if(xInfo->supportsService(u"com.sun.star.form.component.DateField"_ustr) ||
5881 xInfo->supportsService(u"com.sun.star.form.component.ComboBox"_ustr))
5882 return false;
5884 oox::ole::OleFormCtrlExportHelper exportHelper(comphelper::getProcessComponentContext(), xModel, xControlModel);
5885 return exportHelper.isValid();
5888 void DocxAttributeOutput::PostponeOLE( SwOLENode& rNode, const Size& rSize, const SwFlyFrameFormat* pFlyFrameFormat )
5890 if( !m_oPostponedOLEs )
5891 //cannot be postponed, try to write now
5892 WriteOLE( rNode, rSize, pFlyFrameFormat );
5893 else
5894 m_oPostponedOLEs->push_back( PostponedOLE( &rNode, rSize, pFlyFrameFormat ) );
5898 * Write w:object hierarchy for embedded objects after end element of w:rPr tag.
5900 void DocxAttributeOutput::WritePostponedOLE()
5902 if( !m_oPostponedOLEs )
5903 return;
5905 for( const auto & rPostponedOLE : *m_oPostponedOLEs )
5907 WriteOLE( *rPostponedOLE.object, rPostponedOLE.size, rPostponedOLE.frame );
5910 // clear list of postponed objects
5911 m_oPostponedOLEs.reset();
5914 void DocxAttributeOutput::WriteOLE( SwOLENode& rNode, const Size& rSize, const SwFlyFrameFormat* pFlyFrameFormat )
5916 OSL_ASSERT(pFlyFrameFormat);
5918 // get interoperability information about embedded objects
5919 rtl::Reference< SwXTextDocument > xPropSet( m_rExport.m_rDoc.GetDocShell()->GetBaseModel() );
5920 uno::Sequence< beans::PropertyValue > aGrabBag, aObjectsInteropList,aObjectInteropAttributes;
5921 xPropSet->getPropertyValue( UNO_NAME_MISC_OBJ_INTEROPGRABBAG ) >>= aGrabBag;
5922 auto pProp = std::find_if(std::cbegin(aGrabBag), std::cend(aGrabBag),
5923 [](const beans::PropertyValue& rProp) { return rProp.Name == "EmbeddedObjects"; });
5924 if (pProp != std::cend(aGrabBag))
5925 pProp->Value >>= aObjectsInteropList;
5927 SwOLEObj& aObject = rNode.GetOLEObj();
5928 uno::Reference < embed::XEmbeddedObject > xObj( aObject.GetOleRef() );
5929 comphelper::EmbeddedObjectContainer* aContainer = aObject.GetObject().GetContainer();
5930 OUString sObjectName = aContainer->GetEmbeddedObjectName( xObj );
5932 // set some attributes according to the type of the embedded object
5933 OUString sProgID, sDrawAspect;
5934 switch (rNode.GetAspect())
5936 case embed::Aspects::MSOLE_CONTENT: sDrawAspect = "Content"; break;
5937 case embed::Aspects::MSOLE_DOCPRINT: sDrawAspect = "DocPrint"; break;
5938 case embed::Aspects::MSOLE_ICON: sDrawAspect = "Icon"; break;
5939 case embed::Aspects::MSOLE_THUMBNAIL: sDrawAspect = "Thumbnail"; break;
5940 default:
5941 SAL_WARN("sw.ww8", "DocxAttributeOutput::WriteOLE: invalid aspect value");
5943 auto pObjectsInterop = std::find_if(std::cbegin(aObjectsInteropList), std::cend(aObjectsInteropList),
5944 [&sObjectName](const beans::PropertyValue& rProp) { return rProp.Name == sObjectName; });
5945 if (pObjectsInterop != std::cend(aObjectsInteropList))
5946 pObjectsInterop->Value >>= aObjectInteropAttributes;
5948 for (const auto& rObjectInteropAttribute : aObjectInteropAttributes)
5950 if ( rObjectInteropAttribute.Name == "ProgID" )
5952 rObjectInteropAttribute.Value >>= sProgID;
5956 // write embedded file
5957 OString sId = m_rExport.WriteOLEObject(aObject, sProgID);
5959 if( sId.isEmpty() )
5961 // the embedded file could not be saved
5962 // fallback: save as an image
5963 FlyFrameGraphic( nullptr, rSize, pFlyFrameFormat, &rNode );
5964 return;
5967 // write preview image
5968 const Graphic* pGraphic = rNode.GetGraphic();
5969 Graphic aGraphic = pGraphic ? *pGraphic : Graphic();
5970 m_rDrawingML.SetFS(m_pSerializer);
5971 OUString sImageId = m_rDrawingML.writeGraphicToStorage(aGraphic);
5973 if ( sDrawAspect == "Content" )
5977 awt::Size aSize = xObj->getVisualAreaSize( rNode.GetAspect() );
5979 MapUnit aUnit = VCLUnoHelper::UnoEmbed2VCLMapUnit( xObj->getMapUnit( rNode.GetAspect() ) );
5980 Size aOriginalSize( OutputDevice::LogicToLogic(Size( aSize.Width, aSize.Height),
5981 MapMode(aUnit), MapMode(MapUnit::MapTwip)));
5983 m_pSerializer->startElementNS( XML_w, XML_object,
5984 FSNS(XML_w, XML_dxaOrig), OString::number(aOriginalSize.Width()),
5985 FSNS(XML_w, XML_dyaOrig), OString::number(aOriginalSize.Height()) );
5987 catch ( uno::Exception& )
5989 m_pSerializer->startElementNS(XML_w, XML_object);
5992 else
5994 m_pSerializer->startElementNS(XML_w, XML_object);
5997 OString sShapeId = "ole_" + sId;
5999 //OLE Shape definition
6000 WriteOLEShape(*pFlyFrameFormat, rSize, sShapeId, sImageId);
6002 //OLE Object definition
6003 m_pSerializer->singleElementNS(XML_o, XML_OLEObject,
6004 XML_Type, "Embed",
6005 XML_ProgID, sProgID,
6006 XML_ShapeID, sShapeId,
6007 XML_DrawAspect, sDrawAspect,
6008 XML_ObjectID, "_" + OString::number(comphelper::rng::uniform_int_distribution(0, std::numeric_limits<int>::max())),
6009 FSNS( XML_r, XML_id ), sId );
6011 m_pSerializer->endElementNS(XML_w, XML_object);
6014 void DocxAttributeOutput::WriteOLEShape(const SwFlyFrameFormat& rFrameFormat, const Size& rSize,
6015 std::string_view rShapeId, const OUString& rImageId)
6017 assert(m_pSerializer);
6019 //Here is an attribute list where we collect the attributes what we want to export
6020 rtl::Reference<FastAttributeList> pAttr = FastSerializerHelper::createAttrList();
6021 pAttr->add(XML_id, rShapeId);
6023 //export the fixed shape type for picture frame
6024 m_pSerializer->write(vml::VMLExport::GetVMLShapeTypeDefinition(rShapeId, true));
6025 pAttr->add(XML_type, OString::Concat("_x0000_t") + rShapeId);
6027 //Export the style attribute for position and size
6028 pAttr->add(XML_style, GetOLEStyle(rFrameFormat, rSize));
6029 //Get the OLE frame
6030 const SvxBoxItem& rBox = rFrameFormat.GetAttrSet().GetBox();
6031 OString sLineType;
6032 OString sDashType;
6033 //Word does not handle differently the four sides,
6034 //so we have to choose, and the left one is the winner:
6035 if (rBox.GetLeft())
6037 //Get the left border color and width
6038 const Color aLineColor = rBox.GetLeft()->GetColor();
6039 const tools::Long aLineWidth = rBox.GetLeft()->GetWidth();
6041 //Convert the left OLE border style to OOXML
6042 //FIXME improve if it's necessary
6043 switch (rBox.GetLeft()->GetBorderLineStyle())
6045 case SvxBorderLineStyle::SOLID:
6046 sLineType = "Single"_ostr;
6047 sDashType = "Solid"_ostr;
6048 break;
6049 case SvxBorderLineStyle::DASHED:
6050 sLineType = "Single"_ostr;
6051 sDashType = "Dash"_ostr;
6052 break;
6053 case SvxBorderLineStyle::DASH_DOT:
6054 sLineType = "Single"_ostr;
6055 sDashType = "DashDot"_ostr;
6056 break;
6057 case SvxBorderLineStyle::DASH_DOT_DOT:
6058 sLineType = "Single"_ostr;
6059 sDashType = "ShortDashDotDot"_ostr;
6060 break;
6061 case SvxBorderLineStyle::DOTTED:
6062 sLineType = "Single"_ostr;
6063 sDashType = "Dot"_ostr;
6064 break;
6065 case SvxBorderLineStyle::DOUBLE:
6066 sLineType = "ThinThin"_ostr;
6067 sDashType = "Solid"_ostr;
6068 break;
6069 case SvxBorderLineStyle::DOUBLE_THIN:
6070 sLineType = "ThinThin"_ostr;
6071 sDashType = "Solid"_ostr;
6072 break;
6073 case SvxBorderLineStyle::EMBOSSED:
6074 sLineType = "Single"_ostr;
6075 sDashType = "Solid"_ostr;
6076 break;
6077 case SvxBorderLineStyle::ENGRAVED:
6078 sLineType = "Single"_ostr;
6079 sDashType = "Solid"_ostr;
6080 break;
6081 case SvxBorderLineStyle::FINE_DASHED:
6082 sLineType = "Single"_ostr;
6083 sDashType = "Dot"_ostr;
6084 break;
6085 case SvxBorderLineStyle::INSET:
6086 sLineType = "Single"_ostr;
6087 sDashType = "Solid"_ostr;
6088 break;
6089 case SvxBorderLineStyle::OUTSET:
6090 sLineType = "Single"_ostr;
6091 sDashType = "Solid"_ostr;
6092 break;
6093 case SvxBorderLineStyle::THICKTHIN_LARGEGAP:
6094 case SvxBorderLineStyle::THICKTHIN_MEDIUMGAP:
6095 case SvxBorderLineStyle::THICKTHIN_SMALLGAP:
6096 sLineType = "ThickThin"_ostr;
6097 sDashType = "Solid"_ostr;
6098 break;
6099 case SvxBorderLineStyle::THINTHICK_LARGEGAP:
6100 case SvxBorderLineStyle::THINTHICK_MEDIUMGAP:
6101 case SvxBorderLineStyle::THINTHICK_SMALLGAP:
6102 sLineType = "ThinThick"_ostr;
6103 sDashType = "Solid"_ostr;
6104 break;
6105 case SvxBorderLineStyle::NONE:
6106 sLineType = ""_ostr;
6107 sDashType = ""_ostr;
6108 break;
6109 default:
6110 SAL_WARN("sw.ww8", "Unknown line type on OOXML ELE export!");
6111 break;
6114 //If there is a line add it for export
6115 if (!sLineType.isEmpty() && !sDashType.isEmpty())
6117 pAttr->add(XML_stroked, "t");
6118 pAttr->add(XML_strokecolor, "#" + msfilter::util::ConvertColor(aLineColor));
6119 pAttr->add(XML_strokeweight, OString::number(aLineWidth / 20) + "pt");
6123 //Let's check the filltype of the OLE
6124 switch (rFrameFormat.GetAttrSet().Get(XATTR_FILLSTYLE).GetValue())
6126 case drawing::FillStyle::FillStyle_SOLID:
6128 //If solid, we get the color and add it to the exporter
6129 const Color rShapeColor = rFrameFormat.GetAttrSet().Get(XATTR_FILLCOLOR).GetColorValue();
6130 pAttr->add(XML_filled, "t");
6131 pAttr->add(XML_fillcolor, "#" + msfilter::util::ConvertColor(rShapeColor));
6132 break;
6134 case drawing::FillStyle::FillStyle_GRADIENT:
6135 case drawing::FillStyle::FillStyle_HATCH:
6136 case drawing::FillStyle::FillStyle_BITMAP:
6137 //TODO
6138 break;
6139 case drawing::FillStyle::FillStyle_NONE:
6141 pAttr->add(XML_filled, "f");
6142 break;
6144 default:
6145 SAL_WARN("sw.ww8", "Unknown fill type on OOXML OLE export!");
6146 break;
6148 pAttr->addNS(XML_o, XML_ole, ""); //compulsory, even if it's empty
6149 m_pSerializer->startElementNS(XML_v, XML_shape, pAttr);//Write the collected attrs...
6151 if (!sLineType.isEmpty() && !sDashType.isEmpty()) //If there is a line/dash style it is time to export it
6153 m_pSerializer->singleElementNS(XML_v, XML_stroke, XML_linestyle, sLineType, XML_dashstyle, sDashType);
6156 // shape filled with the preview image
6157 m_pSerializer->singleElementNS(XML_v, XML_imagedata,
6158 FSNS(XML_r, XML_id), rImageId,
6159 FSNS(XML_o, XML_title), "");
6161 //export wrap settings
6162 if (rFrameFormat.GetAnchor().GetAnchorId() != RndStdIds::FLY_AS_CHAR) //As-char objs does not have surround.
6163 ExportOLESurround(rFrameFormat.GetSurround());
6165 m_pSerializer->endElementNS(XML_v, XML_shape);
6168 OString DocxAttributeOutput::GetOLEStyle(const SwFlyFrameFormat& rFormat, const Size& rSize)
6170 //tdf#131539: Export OLE positions in docx:
6171 //This string will store the position output for the xml
6172 OString aPos;
6173 //This string will store the relative position for aPos
6174 OString aAnch;
6176 if (rFormat.GetAnchor().GetAnchorId() != RndStdIds::FLY_AS_CHAR)
6178 //Get the horizontal alignment of the OLE via the frame format, to aHAlign
6179 OString aHAlign = convertToOOXMLHoriOrient(rFormat.GetHoriOrient().GetHoriOrient(),
6180 rFormat.GetHoriOrient().IsPosToggle());
6181 //Get the vertical alignment of the OLE via the frame format to aVAlign
6182 OString aVAlign = convertToOOXMLVertOrient(rFormat.GetVertOrient().GetVertOrient());
6184 // Check if the OLE anchored to page:
6185 const bool bIsPageAnchor = rFormat.GetAnchor().GetAnchorId() == RndStdIds::FLY_AT_PAGE;
6187 //Get the relative horizontal positions for the anchors
6188 OString aHAnch
6189 = bIsPageAnchor
6190 ? "page"_ostr
6191 : convertToOOXMLHoriOrientRel(rFormat.GetHoriOrient().GetRelationOrient());
6192 //Get the relative vertical positions for the anchors
6193 OString aVAnch = convertToOOXMLVertOrientRel(rFormat.GetVertOrient().GetRelationOrient());
6195 //Choice that the horizontal position is relative or not
6196 if (!aHAlign.isEmpty())
6197 aHAlign = ";mso-position-horizontal:" + aHAlign;
6198 aHAlign = ";mso-position-horizontal-relative:" + aHAnch;
6200 //Choice that the vertical position is relative or not
6201 if (!aVAlign.isEmpty())
6202 aVAlign = ";mso-position-vertical:" + aVAlign;
6203 aVAlign = ";mso-position-vertical-relative:" + aVAnch;
6205 //Set the anchoring information into one string for aPos
6206 aAnch = aHAlign + aVAlign;
6208 //Query the positions to aPos from frameformat
6209 aPos =
6210 "position:absolute;margin-left:" + OString::number(double(rFormat.GetHoriOrient().GetPos()) / 20) +
6211 "pt;margin-top:" + OString::number(double(rFormat.GetVertOrient().GetPos()) / 20) + "pt;";
6214 OString sShapeStyle = "width:" + OString::number( double( rSize.Width() ) / 20 ) +
6215 "pt;height:" + OString::number( double( rSize.Height() ) / 20 ) +
6216 "pt"; //from VMLExport::AddRectangleDimensions(), it does: value/20
6218 const SvxLRSpaceItem& rLRSpace = rFormat.GetLRSpace();
6219 if (rLRSpace.IsExplicitZeroMarginValLeft() || rLRSpace.ResolveLeft({}))
6220 sShapeStyle += ";mso-wrap-distance-left:"
6221 + OString::number(double(rLRSpace.ResolveLeft({})) / 20) + "pt";
6222 if (rLRSpace.IsExplicitZeroMarginValRight() || rLRSpace.ResolveRight({}))
6223 sShapeStyle += ";mso-wrap-distance-right:"
6224 + OString::number(double(rLRSpace.ResolveRight({})) / 20) + "pt";
6225 const SvxULSpaceItem& rULSpace = rFormat.GetULSpace();
6226 if (rULSpace.GetUpper())
6227 sShapeStyle += ";mso-wrap-distance-top:" + OString::number(double(rULSpace.GetUpper()) / 20) + "pt";
6228 if (rULSpace.GetLower())
6229 sShapeStyle += ";mso-wrap-distance-bottom:" + OString::number(double(rULSpace.GetLower()) / 20) + "pt";
6231 //Export anchor setting, if it exists
6232 if (!aPos.isEmpty() && !aAnch.isEmpty())
6233 sShapeStyle = aPos + sShapeStyle + aAnch;
6235 return sShapeStyle;
6238 void DocxAttributeOutput::ExportOLESurround(const SwFormatSurround& rWrap)
6240 const bool bIsContour = rWrap.IsContour(); //Has the shape contour or not
6241 OString sSurround;
6242 OString sSide;
6244 //Map the ODF wrap settings to OOXML one
6245 switch (rWrap.GetSurround())
6247 case text::WrapTextMode::WrapTextMode_NONE:
6248 sSurround = "topAndBottom"_ostr;
6249 break;
6250 case text::WrapTextMode::WrapTextMode_PARALLEL:
6251 sSurround = bIsContour ? "tight"_ostr : "square"_ostr;
6252 break;
6253 case text::WrapTextMode::WrapTextMode_DYNAMIC:
6254 sSide = "largest"_ostr;
6255 sSurround = bIsContour ? "tight"_ostr : "square"_ostr;
6256 break;
6257 case text::WrapTextMode::WrapTextMode_LEFT:
6258 sSide = "left"_ostr;
6259 sSurround = bIsContour ? "tight"_ostr : "square"_ostr;
6260 break;
6261 case text::WrapTextMode::WrapTextMode_RIGHT:
6262 sSide = "right"_ostr;
6263 sSurround = bIsContour ? "tight"_ostr : "square"_ostr;
6264 break;
6265 default:
6266 SAL_WARN("sw.ww8", "Unknown surround type on OOXML export!");
6267 break;
6270 //if there is a setting export it:
6271 if (!sSurround.isEmpty())
6273 if (sSide.isEmpty())
6274 m_pSerializer->singleElementNS(XML_w10, XML_wrap, XML_type, sSurround);
6275 else
6276 m_pSerializer->singleElementNS(XML_w10, XML_wrap, XML_type, sSurround, XML_side, sSide);
6280 void DocxAttributeOutput::WritePostponedCustomShape()
6282 if (!m_oPostponedCustomShape)
6283 return;
6285 for( const auto & rPostponedDrawing : *m_oPostponedCustomShape)
6287 m_rExport.GetFilter().SetMaxDocId(m_anchorId + 1);
6288 if ( IsAlternateContentChoiceOpen() )
6289 m_rExport.SdrExporter().writeDMLDrawing(rPostponedDrawing.object, rPostponedDrawing.frame, m_anchorId++);
6290 else
6291 m_rExport.SdrExporter().writeDMLAndVMLDrawing(rPostponedDrawing.object, *rPostponedDrawing.frame, m_anchorId++);
6292 m_anchorId = m_rExport.GetFilter().GetMaxDocId();
6294 m_oPostponedCustomShape.reset();
6297 void DocxAttributeOutput::WritePostponedDMLDrawing()
6299 if (!m_oPostponedDMLDrawings)
6300 return;
6302 // Clear the list early, this method may be called recursively.
6303 std::optional< std::vector<PostponedDrawing> > pPostponedDMLDrawings(std::move(m_oPostponedDMLDrawings));
6304 std::optional< std::vector<PostponedOLE> > pPostponedOLEs(std::move(m_oPostponedOLEs));
6305 m_oPostponedDMLDrawings.reset();
6306 m_oPostponedOLEs.reset();
6308 for( const auto & rPostponedDrawing : *pPostponedDMLDrawings )
6310 // Avoid w:drawing within another w:drawing.
6311 m_rExport.GetFilter().SetMaxDocId(m_anchorId + 1);
6312 if ( IsAlternateContentChoiceOpen() && !( m_rExport.SdrExporter().IsDrawingOpen()) )
6313 m_rExport.SdrExporter().writeDMLDrawing(rPostponedDrawing.object, rPostponedDrawing.frame, m_anchorId++);
6314 else
6315 m_rExport.SdrExporter().writeDMLAndVMLDrawing(rPostponedDrawing.object, *rPostponedDrawing.frame, m_anchorId++);
6316 m_anchorId = m_rExport.GetFilter().GetMaxDocId();
6319 m_oPostponedOLEs = std::move(pPostponedOLEs);
6322 void DocxAttributeOutput::WriteFlyFrame(const ww8::Frame& rFrame)
6324 m_pSerializer->mark(Tag_OutputFlyFrame);
6326 switch ( rFrame.GetWriterType() )
6328 case ww8::Frame::eGraphic:
6330 const SdrObject* pSdrObj = rFrame.GetFrameFormat().FindRealSdrObject();
6331 const SwNode *pNode = rFrame.GetContent();
6332 const SwGrfNode *pGrfNode = pNode ? pNode->GetGrfNode() : nullptr;
6333 if ( pGrfNode )
6335 if (!m_oPostponedGraphic)
6337 m_bPostponedProcessingFly = false ;
6338 FlyFrameGraphic( pGrfNode, rFrame.GetLayoutSize(), nullptr, nullptr, pSdrObj);
6340 else // we are writing out attributes, but w:drawing should not be inside w:rPr,
6341 { // so write it out later
6342 m_bPostponedProcessingFly = true ;
6343 m_oPostponedGraphic->push_back(PostponedGraphic(pGrfNode, rFrame.GetLayoutSize(), pSdrObj));
6347 break;
6348 case ww8::Frame::eDrawing:
6350 const SdrObject* pSdrObj = rFrame.GetFrameFormat().FindRealSdrObject();
6351 if ( pSdrObj )
6353 const bool bIsDiagram(nullptr != pSdrObj && pSdrObj->isDiagram());
6355 if (bIsDiagram)
6357 if ( !m_oPostponedDiagrams )
6359 m_bPostponedProcessingFly = false ;
6360 m_rExport.SdrExporter().writeDiagram( pSdrObj, rFrame.GetFrameFormat(), m_anchorId++);
6362 else // we are writing out attributes, but w:drawing should not be inside w:rPr,
6363 { // so write it out later
6364 m_bPostponedProcessingFly = true ;
6365 m_oPostponedDiagrams->push_back( PostponedDiagram( pSdrObj, &(rFrame.GetFrameFormat()) ));
6368 else
6370 if (!m_oPostponedDMLDrawings)
6372 m_rExport.GetFilter().SetMaxDocId(m_anchorId + 1);
6373 if ( IsAlternateContentChoiceOpen() )
6375 // Do not write w:drawing inside w:drawing. Instead Postpone the Inner Drawing.
6376 if( m_rExport.SdrExporter().IsDrawingOpen() )
6377 m_oPostponedCustomShape->push_back(PostponedDrawing(pSdrObj, &(rFrame.GetFrameFormat())));
6378 else
6379 m_rExport.SdrExporter().writeDMLDrawing( pSdrObj, &rFrame.GetFrameFormat(), m_anchorId++);
6381 else
6382 m_rExport.SdrExporter().writeDMLAndVMLDrawing( pSdrObj, rFrame.GetFrameFormat(), m_anchorId++);
6383 m_anchorId = m_rExport.GetFilter().GetMaxDocId();
6385 m_bPostponedProcessingFly = false ;
6387 // IsAlternateContentChoiceOpen(): check is to ensure that only one object is getting added. Without this check, plus one object gets added
6388 // m_bParagraphFrameOpen: check if the frame is open.
6389 else if (IsAlternateContentChoiceOpen() && m_bParagraphFrameOpen)
6390 m_oPostponedCustomShape->push_back(PostponedDrawing(pSdrObj, &(rFrame.GetFrameFormat())));
6391 else
6393 // we are writing out attributes, but w:drawing should not be inside w:rPr, so write it out later
6394 m_bPostponedProcessingFly = true ;
6395 m_oPostponedDMLDrawings->push_back(PostponedDrawing(pSdrObj, &(rFrame.GetFrameFormat())));
6400 break;
6401 case ww8::Frame::eTextBox:
6403 // If this is a TextBox of a shape, then ignore: it's handled in WriteTextBox().
6404 if (DocxSdrExport::isTextBox(rFrame.GetFrameFormat()))
6405 break;
6407 // If this is a TextBox containing a table which we already exported directly, ignore it
6408 if (m_aFloatingTablesOfParagraph.find(&rFrame.GetFrameFormat()) != m_aFloatingTablesOfParagraph.end())
6409 break;
6411 // skip also inline headings already exported before
6412 const SwFormat* pParent = rFrame.GetFrameFormat().DerivedFrom();
6413 if ( pParent && pParent->GetPoolFormatId() == RES_POOLFRM_INLINE_HEADING )
6414 break;
6416 // The frame output is postponed to the end of the anchor paragraph
6417 bool bDuplicate = false;
6418 const OUString& rName = rFrame.GetFrameFormat().GetName();
6419 if (m_aFramesOfParagraph.size() && !rName.isEmpty())
6421 const unsigned nSize = m_aFramesOfParagraph.top().size();
6422 for (unsigned nIndex = 0; nIndex < nSize; ++nIndex)
6424 const OUString& rNameExisting
6425 = m_aFramesOfParagraph.top()[nIndex].GetFrameFormat().GetName();
6427 if (rName == rNameExisting)
6429 bDuplicate = true;
6430 break;
6435 if( !bDuplicate )
6437 m_bPostponedProcessingFly = true ;
6438 if ( m_aFramesOfParagraph.size() )
6439 m_aFramesOfParagraph.top().emplace_back(rFrame);
6442 break;
6443 case ww8::Frame::eOle:
6445 const SwFrameFormat &rFrameFormat = rFrame.GetFrameFormat();
6446 const SdrObject *pSdrObj = rFrameFormat.FindRealSdrObject();
6447 if ( pSdrObj )
6449 SwNodeIndex aIdx(*rFrameFormat.GetContent().GetContentIdx(), 1);
6450 SwOLENode& rOLENd = *aIdx.GetNode().GetOLENode();
6452 //output variable for the formula alignment (default inline)
6453 sal_Int8 nAlign(FormulaImExportBase::eFormulaAlign::INLINE);
6454 auto xObj(rOLENd.GetOLEObj().GetOleRef()); //get the xObject of the formula
6456 //tdf133030: Export formula position
6457 //If we have a formula with inline anchor...
6458 if(SotExchange::IsMath(xObj->getClassID()) && rFrame.IsInline())
6460 SwNode const* const pAnchorNode = rFrameFormat.GetAnchor().GetAnchorNode();
6461 if(pAnchorNode)
6463 //Get the text node what the formula anchored to
6464 const SwTextNode* pTextNode = pAnchorNode->GetTextNode();
6465 if(pTextNode && pTextNode->Len() == 1)
6467 //Get the paragraph alignment
6468 auto aParaAdjust = pTextNode->GetSwAttrSet().GetAdjust().GetAdjust();
6469 //And set the formula according to the paragraph alignment
6470 if (aParaAdjust == SvxAdjust::Center)
6471 nAlign = FormulaImExportBase::eFormulaAlign::CENTER;
6472 else if (aParaAdjust == SvxAdjust::Right)
6473 nAlign = FormulaImExportBase::eFormulaAlign::RIGHT;
6474 else // left in the case of left and justified paragraph alignments
6475 nAlign = FormulaImExportBase::eFormulaAlign::LEFT;
6479 WriteOLE2Obj( pSdrObj, rOLENd, rFrame.GetLayoutSize(), dynamic_cast<const SwFlyFrameFormat*>( &rFrameFormat ), nAlign);
6480 m_bPostponedProcessingFly = false ;
6483 break;
6484 case ww8::Frame::eFormControl:
6486 const SdrObject* pObject = rFrame.GetFrameFormat().FindRealSdrObject();
6487 if(ExportAsActiveXControl(pObject))
6488 m_aPostponedActiveXControls.emplace_back(pObject, &(rFrame.GetFrameFormat()));
6489 else
6490 m_aPostponedFormControls.push_back(pObject);
6491 m_bPostponedProcessingFly = true ;
6493 break;
6494 default:
6495 SAL_INFO("sw.ww8", "TODO DocxAttributeOutput::OutputFlyFrame_Impl( const ww8::Frame& rFrame ) - frame type " <<
6496 ( rFrame.GetWriterType() == ww8::Frame::eTextBox ? "eTextBox":
6497 ( rFrame.GetWriterType() == ww8::Frame::eOle ? "eOle": "???" ) ) );
6498 break;
6501 m_pSerializer->mergeTopMarks(Tag_OutputFlyFrame);
6504 void DocxAttributeOutput::OutputFlyFrame_Impl(const ww8::Frame& rFrame, const Point& /*rNdTopLeft*/)
6506 /// The old OutputFlyFrame_Impl() moved to WriteFlyFrame().
6507 /// Now if a frame anchored inside another frame, it will
6508 /// not be exported immediately, because OOXML does not
6509 /// support that feature, instead it postponed and exported
6510 /// later when the original shape closed.
6512 if (rFrame.IsInline())
6514 m_nEmbedFlyLevel++;
6515 WriteFlyFrame(rFrame);
6516 m_nEmbedFlyLevel--;
6517 return;
6520 if (m_nEmbedFlyLevel == 0)
6522 if (m_vPostponedFlys.empty())
6524 m_nEmbedFlyLevel++;
6525 WriteFlyFrame(rFrame);
6526 m_nEmbedFlyLevel--;
6528 else
6529 for (auto it = m_vPostponedFlys.begin(); it != m_vPostponedFlys.end();)
6531 m_nEmbedFlyLevel++;
6532 WriteFlyFrame(*it);
6533 it = m_vPostponedFlys.erase(it);
6534 m_nEmbedFlyLevel--;
6537 else
6539 bool bFound = false;
6540 for (const auto& i : m_vPostponedFlys)
6542 if (i.RefersToSameFrameAs(rFrame))
6544 bFound = true;
6545 break;
6548 if (!bFound)
6550 if (auto pParentFly = rFrame.GetContentNode()->GetFlyFormat())
6552 auto aHori(rFrame.GetFrameFormat().GetHoriOrient());
6553 aHori.SetPos(aHori.GetPos() + pParentFly->GetHoriOrient().GetPos());
6554 auto aVori(rFrame.GetFrameFormat().GetVertOrient());
6555 aVori.SetPos(aVori.GetPos() + pParentFly->GetVertOrient().GetPos());
6557 const_cast<SwFrameFormat&>(rFrame.GetFrameFormat()).SetFormatAttr(aHori);
6558 const_cast<SwFrameFormat&>(rFrame.GetFrameFormat()).SetFormatAttr(aVori);
6559 const_cast<SwFrameFormat&>(rFrame.GetFrameFormat()).SetFormatAttr(pParentFly->GetAnchor());
6561 m_vPostponedFlys.push_back(rFrame);
6568 void DocxAttributeOutput::WriteOutliner(const OutlinerParaObject& rParaObj)
6570 const EditTextObject& rEditObj = rParaObj.GetTextObject();
6571 MSWord_SdrAttrIter aAttrIter( m_rExport, rEditObj, TXT_HFTXTBOX );
6573 sal_Int32 nPara = rEditObj.GetParagraphCount();
6575 m_pSerializer->startElementNS(XML_w, XML_txbxContent);
6576 for (sal_Int32 n = 0; n < nPara; ++n)
6578 if( n )
6579 aAttrIter.NextPara( n );
6581 OUString aStr( rEditObj.GetText( n ));
6582 sal_Int32 nCurrentPos = 0;
6583 sal_Int32 nEnd = aStr.getLength();
6585 StartParagraph(ww8::WW8TableNodeInfo::Pointer_t(), false);
6587 // Write paragraph properties.
6588 StartParagraphProperties();
6589 aAttrIter.OutParaAttr(false);
6590 SfxItemSet aParagraphMarkerProperties(m_rExport.m_rDoc.GetAttrPool());
6591 EndParagraphProperties(aParagraphMarkerProperties, nullptr, nullptr, nullptr);
6593 do {
6594 const sal_Int32 nNextAttr = std::min(aAttrIter.WhereNext(), nEnd);
6596 m_pSerializer->startElementNS(XML_w, XML_r);
6598 // Write run properties.
6599 m_pSerializer->startElementNS(XML_w, XML_rPr);
6600 aAttrIter.OutAttr(nCurrentPos);
6601 WriteCollectedRunProperties();
6602 m_pSerializer->endElementNS(XML_w, XML_rPr);
6604 bool bTextAtr = aAttrIter.IsTextAttr( nCurrentPos );
6605 if( !bTextAtr )
6607 OUString aOut( aStr.copy( nCurrentPos, nNextAttr - nCurrentPos ) );
6608 RunText(aOut);
6611 if ( !m_sRawText.isEmpty() )
6613 RunText( m_sRawText );
6614 m_sRawText.clear();
6617 m_pSerializer->endElementNS( XML_w, XML_r );
6619 nCurrentPos = nNextAttr;
6620 aAttrIter.NextPos();
6622 while( nCurrentPos < nEnd );
6623 EndParagraph(ww8::WW8TableNodeInfoInner::Pointer_t());
6625 m_pSerializer->endElementNS( XML_w, XML_txbxContent );
6628 void DocxAttributeOutput::pushToTableExportContext(DocxTableExportContext& rContext)
6630 rContext.m_pTableInfo = m_rExport.m_pTableInfo;
6631 m_rExport.m_pTableInfo = std::make_shared<ww8::WW8TableInfo>();
6633 rContext.m_bTableCellOpen = m_tableReference.m_bTableCellOpen;
6634 m_tableReference.m_bTableCellOpen = false;
6636 rContext.m_nTableDepth = m_tableReference.m_nTableDepth;
6637 m_tableReference.m_nTableDepth = 0;
6639 rContext.m_bStartedParaSdt = m_aParagraphSdt.m_bStartedSdt;
6640 m_aParagraphSdt.m_bStartedSdt = false;
6641 rContext.m_bStartedRunSdt = m_aRunSdt.m_bStartedSdt;
6642 m_aRunSdt.m_bStartedSdt = false;
6644 rContext.m_nHyperLinkCount = m_nHyperLinkCount.back();
6645 m_nHyperLinkCount.back() = 0;
6648 void DocxAttributeOutput::popFromTableExportContext(DocxTableExportContext const & rContext)
6650 m_rExport.m_pTableInfo = rContext.m_pTableInfo;
6651 m_tableReference.m_bTableCellOpen = rContext.m_bTableCellOpen;
6652 m_tableReference.m_nTableDepth = rContext.m_nTableDepth;
6653 m_aParagraphSdt.m_bStartedSdt = rContext.m_bStartedParaSdt;
6654 m_aRunSdt.m_bStartedSdt = rContext.m_bStartedRunSdt;
6655 m_nHyperLinkCount.back() = rContext.m_nHyperLinkCount;
6658 void DocxAttributeOutput::WriteTextBox(uno::Reference<drawing::XShape> xShape)
6660 DocxTableExportContext aTableExportContext(*this);
6662 SwFrameFormat* pTextBox = SwTextBoxHelper::getOtherTextBoxFormat(xShape);
6663 assert(pTextBox);
6664 const SwPosition* pAnchor = nullptr;
6665 const bool bFlyAtPage = pTextBox->GetAnchor().GetAnchorId() == RndStdIds::FLY_AT_PAGE;
6666 if (bFlyAtPage) //tdf135711
6668 auto pNdIdx = pTextBox->GetContent().GetContentIdx();
6669 if (pNdIdx) //Is that possible it is null?
6670 pAnchor = new SwPosition(*pNdIdx);
6672 else
6674 pAnchor = pTextBox->GetAnchor().GetContentAnchor();//This might be null
6677 if (pAnchor) //pAnchor can be null, so that's why not assert here.
6679 ww8::Frame aFrame(*pTextBox, *pAnchor);
6680 m_rExport.SdrExporter().writeDMLTextFrame(&aFrame, m_anchorId++, /*bTextBoxOnly=*/true);
6681 if (bFlyAtPage)
6683 delete pAnchor;
6688 void DocxAttributeOutput::WriteVMLTextBox(uno::Reference<drawing::XShape> xShape)
6690 DocxTableExportContext aTableExportContext(*this);
6692 SwFrameFormat* pTextBox = SwTextBoxHelper::getOtherTextBoxFormat(xShape);
6693 assert(pTextBox);
6694 const SwPosition* pAnchor = nullptr;
6695 if (pTextBox->GetAnchor().GetAnchorId() == RndStdIds::FLY_AT_PAGE) //tdf135711
6697 auto pNdIdx = pTextBox->GetContent().GetContentIdx();
6698 if (pNdIdx) //Is that possible it is null?
6699 pAnchor = new SwPosition(*pNdIdx);
6701 else
6703 pAnchor = pTextBox->GetAnchor().GetContentAnchor();//This might be null
6706 if (pAnchor) //pAnchor can be null, so that's why not assert here.
6708 ww8::Frame aFrame(*pTextBox, *pAnchor);
6709 m_rExport.SdrExporter().writeVMLTextFrame(&aFrame, /*bTextBoxOnly=*/true);
6710 if (pTextBox->GetAnchor().GetAnchorId() == RndStdIds::FLY_AT_PAGE)
6712 delete pAnchor;
6717 oox::drawingml::DrawingML& DocxAttributeOutput::GetDrawingML()
6719 return m_rDrawingML;
6722 bool DocxAttributeOutput::MaybeOutputBrushItem(SfxItemSet const& rSet)
6724 const XFillStyleItem* pXFillStyleItem(rSet.GetItem<XFillStyleItem>(XATTR_FILLSTYLE));
6726 if ((pXFillStyleItem && pXFillStyleItem->GetValue() != drawing::FillStyle_NONE)
6727 || !m_rExport.SdrExporter().getDMLTextFrameSyntax())
6729 return false;
6732 // sw text frames are opaque by default, even with fill none!
6733 std::unique_ptr<SfxItemSet> const pClone(rSet.Clone());
6734 XFillColorItem const aColor(OUString(), COL_WHITE);
6735 pClone->Put(aColor);
6736 // call getSvxBrushItemForSolid - this also takes XFillTransparenceItem into account
6737 XFillStyleItem const aSolid(drawing::FillStyle_SOLID);
6738 pClone->Put(aSolid);
6739 std::unique_ptr<SvxBrushItem> const pBrush(getSvxBrushItemFromSourceSet(*pClone, RES_BACKGROUND));
6740 FormatBackground(*pBrush);
6741 return true;
6744 namespace {
6746 /// Functor to do case-insensitive ordering of OUString instances.
6747 struct OUStringIgnoreCase
6749 bool operator() (std::u16string_view lhs, std::u16string_view rhs) const
6751 return o3tl::compareToIgnoreAsciiCase(lhs, rhs) < 0;
6757 /// Guesses if a style created in Writer (no grab-bag) should be qFormat or not.
6758 static bool lcl_guessQFormat(const OUString& rName, sal_uInt16 nWwId)
6760 // If the style has no dedicated STI number, then it's probably a custom style -> qFormat.
6761 if (nWwId == ww::stiUser)
6762 return true;
6764 // Allow exported built-in styles UI language neutral
6765 if ( nWwId == ww::stiNormal ||
6766 ( nWwId>= ww::stiLev1 && nWwId <= ww::stiLev9 ) ||
6767 nWwId == ww::stiCaption || nWwId == ww::stiTitle ||
6768 nWwId == ww::stiSubtitle || nWwId == ww::stiStrong ||
6769 nWwId == ww::stiEmphasis )
6770 return true;
6772 static o3tl::sorted_vector<std::u16string_view, OUStringIgnoreCase> const aAllowlist
6774 u"No Spacing",
6775 u"List Paragraph",
6776 u"Quote",
6777 u"Intense Quote",
6778 u"Subtle Emphasis",
6779 u"Intense Emphasis",
6780 u"Subtle Reference",
6781 u"Intense Reference",
6782 u"Book Title",
6783 u"TOC Heading",
6785 // Not custom style? Then we have a list of standard styles which should be qFormat.
6786 return aAllowlist.find(rName) != aAllowlist.end();
6789 void DocxAttributeOutput::StartStyle( const OUString& rName, StyleType eType,
6790 sal_uInt16 nBase, sal_uInt16 nNext, sal_uInt16 nLink, sal_uInt16 nWwId, sal_uInt16 nSlot, bool bAutoUpdate )
6792 bool bQFormat = false, bUnhideWhenUsed = false, bSemiHidden = false, bLocked = false, bDefault = false, bCustomStyle = false;
6793 OUString aRsid, aUiPriority;
6794 rtl::Reference<FastAttributeList> pStyleAttributeList = FastSerializerHelper::createAttrList();
6795 uno::Any aAny;
6796 if (eType == STYLE_TYPE_PARA || eType == STYLE_TYPE_CHAR)
6798 const SwFormat* pFormat = m_rExport.m_pStyles->GetSwFormat(nSlot);
6799 pFormat->GetGrabBagItem(aAny);
6801 else
6803 const SwNumRule* pRule = m_rExport.m_pStyles->GetSwNumRule(nSlot);
6804 pRule->GetGrabBagItem(aAny);
6806 const uno::Sequence<beans::PropertyValue> aGrabBag = aAny.get< uno::Sequence<beans::PropertyValue> >();
6808 for (const auto& rProp : aGrabBag)
6810 if (rProp.Name == "uiPriority")
6811 aUiPriority = rProp.Value.get<OUString>();
6812 else if (rProp.Name == "qFormat")
6813 bQFormat = true;
6814 else if (rProp.Name == "rsid")
6815 aRsid = rProp.Value.get<OUString>();
6816 else if (rProp.Name == "unhideWhenUsed")
6817 bUnhideWhenUsed = true;
6818 else if (rProp.Name == "semiHidden")
6819 bSemiHidden = true;
6820 else if (rProp.Name == "locked")
6821 bLocked = true;
6822 else if (rProp.Name == "default")
6823 bDefault = rProp.Value.get<bool>();
6824 else if (rProp.Name == "customStyle")
6825 bCustomStyle = rProp.Value.get<bool>();
6826 else
6827 SAL_WARN("sw.ww8", "Unhandled style property: " << rProp.Name);
6830 const char* pType = nullptr;
6831 switch (eType)
6833 case STYLE_TYPE_PARA:
6834 pType = "paragraph";
6835 break;
6836 case STYLE_TYPE_CHAR:
6837 pType = "character";
6838 break;
6839 case STYLE_TYPE_LIST: pType = "numbering"; break;
6841 pStyleAttributeList->add(FSNS( XML_w, XML_type ), pType);
6842 pStyleAttributeList->add(FSNS(XML_w, XML_styleId), m_rExport.m_pStyles->GetStyleId(nSlot));
6843 if (bDefault)
6844 pStyleAttributeList->add(FSNS(XML_w, XML_default), "1");
6845 if (bCustomStyle)
6846 pStyleAttributeList->add(FSNS(XML_w, XML_customStyle), "1");
6847 m_pSerializer->startElementNS( XML_w, XML_style, pStyleAttributeList);
6848 m_pSerializer->singleElementNS(XML_w, XML_name, FSNS(XML_w, XML_val), rName);
6850 if ( nBase != 0x0FFF && eType != STYLE_TYPE_LIST)
6852 m_pSerializer->singleElementNS( XML_w, XML_basedOn,
6853 FSNS( XML_w, XML_val ), m_rExport.m_pStyles->GetStyleId(nBase) );
6856 if (nNext != nSlot && nNext != 0x0FFF && eType != STYLE_TYPE_LIST)
6858 m_pSerializer->singleElementNS( XML_w, XML_next,
6859 FSNS( XML_w, XML_val ), m_rExport.m_pStyles->GetStyleId(nNext) );
6862 if (nLink != 0x0FFF && (eType == STYLE_TYPE_PARA || eType == STYLE_TYPE_CHAR))
6864 m_pSerializer->singleElementNS(XML_w, XML_link, FSNS(XML_w, XML_val),
6865 m_rExport.m_pStyles->GetStyleId(nLink));
6868 if ( bAutoUpdate )
6869 m_pSerializer->singleElementNS(XML_w, XML_autoRedefine);
6871 if (!aUiPriority.isEmpty())
6872 m_pSerializer->singleElementNS(XML_w, XML_uiPriority, FSNS(XML_w, XML_val), aUiPriority);
6873 if (bSemiHidden)
6874 m_pSerializer->singleElementNS(XML_w, XML_semiHidden);
6875 if (bUnhideWhenUsed)
6876 m_pSerializer->singleElementNS(XML_w, XML_unhideWhenUsed);
6878 if (bQFormat || lcl_guessQFormat(rName, nWwId))
6879 m_pSerializer->singleElementNS(XML_w, XML_qFormat);
6880 if (bLocked)
6881 m_pSerializer->singleElementNS(XML_w, XML_locked);
6882 if (!aRsid.isEmpty())
6883 m_pSerializer->singleElementNS(XML_w, XML_rsid, FSNS(XML_w, XML_val), aRsid);
6886 void DocxAttributeOutput::EndStyle()
6888 m_pSerializer->endElementNS( XML_w, XML_style );
6891 void DocxAttributeOutput::StartStyleProperties( bool bParProp, sal_uInt16 /*nStyle*/ )
6893 if ( bParProp )
6895 m_pSerializer->startElementNS(XML_w, XML_pPr);
6896 InitCollectedParagraphProperties();
6898 else
6900 m_pSerializer->startElementNS(XML_w, XML_rPr);
6901 InitCollectedRunProperties();
6905 void DocxAttributeOutput::EndStyleProperties( bool bParProp )
6907 if ( bParProp )
6909 WriteCollectedParagraphProperties();
6911 // Merge the marks for the ordered elements
6912 m_pSerializer->mergeTopMarks(Tag_InitCollectedParagraphProperties);
6914 m_pSerializer->endElementNS( XML_w, XML_pPr );
6916 else
6918 WriteCollectedRunProperties();
6920 // Merge the marks for the ordered elements
6921 m_pSerializer->mergeTopMarks(Tag_InitCollectedRunProperties);
6923 m_pSerializer->endElementNS( XML_w, XML_rPr );
6927 void DocxAttributeOutput::OutlineNumbering(sal_uInt8 const /*nLvl*/)
6929 // Handled by ParaOutlineLevel() instead.
6932 void DocxAttributeOutput::ParaOutlineLevel(const SfxUInt16Item& rItem)
6934 sal_uInt16 nOutLvl = std::min(rItem.GetValue(), sal_uInt16(WW8ListManager::nMaxLevel));
6935 // Outline Level: in LO Body Text = 0, in MS Body Text = 9
6936 nOutLvl = nOutLvl ? nOutLvl - 1 : 9;
6937 m_pSerializer->singleElementNS(XML_w, XML_outlineLvl, FSNS(XML_w, XML_val), OString::number(nOutLvl));
6940 void DocxAttributeOutput::PageBreakBefore( bool bBreak )
6942 if ( bBreak )
6943 m_pSerializer->singleElementNS(XML_w, XML_pageBreakBefore);
6944 else
6945 m_pSerializer->singleElementNS( XML_w, XML_pageBreakBefore,
6946 FSNS( XML_w, XML_val ), "false" );
6949 void DocxAttributeOutput::SectionBreak( sal_uInt8 nC, bool bBreakAfter, const WW8_SepInfo* pSectionInfo, bool bExtraPageBreak)
6951 switch ( nC )
6953 case msword::ColumnBreak:
6954 // The column break should be output in the next paragraph...
6955 if ( m_nColBreakStatus == COLBRK_WRITE )
6956 m_nColBreakStatus = COLBRK_WRITEANDPOSTPONE;
6957 else
6958 m_nColBreakStatus = COLBRK_POSTPONE;
6959 break;
6960 case msword::PageBreak:
6961 if ( pSectionInfo )
6963 // Detect when the current node is the last node in the
6964 // document: the last section is written explicitly in
6965 // DocxExport::WriteMainText(), don't duplicate that here.
6966 SwNodeIndex aCurrentNode(m_rExport.m_pCurPam->GetPointNode());
6967 SwNodeIndex aLastNode(m_rExport.m_rDoc.GetNodes().GetEndOfContent(), -1);
6968 bool bEmit = aCurrentNode != aLastNode;
6970 if (!bEmit)
6972 // Need to still emit an empty section at the end of the
6973 // document in case balanced columns are wanted, since the last
6974 // section in Word is always balanced.
6975 sal_uInt16 nColumns = 1;
6976 bool bBalance = false;
6977 if (const SwSectionFormat* pFormat = pSectionInfo->pSectionFormat)
6979 if (pFormat != reinterpret_cast<SwSectionFormat*>(sal_IntPtr(-1)))
6981 nColumns = pFormat->GetCol().GetNumCols();
6982 const SwFormatNoBalancedColumns& rNoBalanced = pFormat->GetBalancedColumns();
6983 bBalance = !rNoBalanced.GetValue();
6986 bEmit = (nColumns > 1 && bBalance);
6989 // don't add section properties if this will be the first
6990 // paragraph in the document
6991 if ( !m_bParagraphOpened && !m_bIsFirstParagraph && bEmit )
6993 // Create a dummy paragraph if needed
6994 m_pSerializer->startElementNS(XML_w, XML_p);
6995 m_pSerializer->startElementNS(XML_w, XML_pPr);
6997 m_rExport.SectionProperties( *pSectionInfo );
6999 m_pSerializer->endElementNS( XML_w, XML_pPr );
7000 if (bExtraPageBreak)
7002 m_pSerializer->startElementNS(XML_w, XML_r);
7003 m_pSerializer->singleElementNS(XML_w, XML_br, FSNS(XML_w, XML_type), "page");
7004 m_pSerializer->endElementNS(XML_w, XML_r);
7006 m_pSerializer->endElementNS( XML_w, XML_p );
7008 else
7010 if (bExtraPageBreak && m_bParagraphOpened)
7012 m_pSerializer->startElementNS(XML_w, XML_r);
7013 m_pSerializer->singleElementNS(XML_w, XML_br, FSNS(XML_w, XML_type), "page");
7014 m_pSerializer->endElementNS(XML_w, XML_r);
7016 // postpone the output of this; it has to be done inside the
7017 // paragraph properties, so remember it until then
7018 m_pSectionInfo.reset( new WW8_SepInfo( *pSectionInfo ));
7021 else if ( m_bParagraphOpened )
7023 if (bBreakAfter)
7024 // tdf#128889
7025 m_bPageBreakAfter = true;
7026 else
7028 m_pSerializer->startElementNS(XML_w, XML_r);
7029 m_pSerializer->singleElementNS(XML_w, XML_br, FSNS(XML_w, XML_type), "page");
7030 m_pSerializer->endElementNS(XML_w, XML_r);
7033 else
7034 m_bPostponedPageBreak = true;
7036 break;
7037 default:
7038 SAL_INFO("sw.ww8", "Unknown section break to write: " << nC );
7039 break;
7043 void DocxAttributeOutput::EndParaSdtBlock()
7045 if (m_aParagraphSdt.m_bStartedSdt)
7047 // Paragraph-level SDT still open? Close it now.
7048 m_aParagraphSdt.EndSdtBlock(m_pSerializer);
7052 void DocxAttributeOutput::StartSection()
7054 m_pSerializer->startElementNS(XML_w, XML_sectPr);
7055 m_bOpenedSectPr = true;
7057 // Write the elements in the spec order
7058 static const sal_Int32 aOrder[] =
7060 FSNS( XML_w, XML_headerReference ),
7061 FSNS( XML_w, XML_footerReference ),
7062 FSNS( XML_w, XML_footnotePr ),
7063 FSNS( XML_w, XML_endnotePr ),
7064 FSNS( XML_w, XML_type ),
7065 FSNS( XML_w, XML_pgSz ),
7066 FSNS( XML_w, XML_pgMar ),
7067 FSNS( XML_w, XML_paperSrc ),
7068 FSNS( XML_w, XML_pgBorders ),
7069 FSNS( XML_w, XML_lnNumType ),
7070 FSNS( XML_w, XML_pgNumType ),
7071 FSNS( XML_w, XML_cols ),
7072 FSNS( XML_w, XML_formProt ),
7073 FSNS( XML_w, XML_vAlign ),
7074 FSNS( XML_w, XML_noEndnote ),
7075 FSNS( XML_w, XML_titlePg ),
7076 FSNS( XML_w, XML_textDirection ),
7077 FSNS( XML_w, XML_bidi ),
7078 FSNS( XML_w, XML_rtlGutter ),
7079 FSNS( XML_w, XML_docGrid ),
7080 FSNS( XML_w, XML_printerSettings ),
7081 FSNS( XML_w, XML_sectPrChange )
7084 // postpone the output so that we can later [in EndParagraphProperties()]
7085 // prepend the properties before the run
7086 // coverity[overrun-buffer-arg : FALSE] - coverity has difficulty with css::uno::Sequence
7087 m_pSerializer->mark(Tag_StartSection, comphelper::containerToSequence(aOrder));
7088 m_bHadSectPr = true;
7091 void DocxAttributeOutput::EndSection()
7093 // Write the section properties
7094 if ( m_pSectionSpacingAttrList.is() )
7096 m_pSerializer->singleElementNS( XML_w, XML_pgMar, detachFrom( m_pSectionSpacingAttrList ) );
7099 // Order the elements
7100 m_pSerializer->mergeTopMarks(Tag_StartSection);
7102 m_pSerializer->endElementNS( XML_w, XML_sectPr );
7103 m_bOpenedSectPr = false;
7106 void DocxAttributeOutput::SectionFormProtection( bool bProtected )
7108 if ( bProtected )
7109 m_pSerializer->singleElementNS(XML_w, XML_formProt, FSNS(XML_w, XML_val), "true");
7110 else
7111 m_pSerializer->singleElementNS(XML_w, XML_formProt, FSNS(XML_w, XML_val), "false");
7114 void DocxAttributeOutput::SectionRtlGutter(const SfxBoolItem& rRtlGutter)
7116 if (!rRtlGutter.GetValue())
7118 return;
7121 m_pSerializer->singleElementNS(XML_w, XML_rtlGutter);
7124 void DocxAttributeOutput::TextLineBreak(const SwFormatLineBreak& rLineBreak)
7126 m_oLineBreakClear = rLineBreak.GetValue();
7129 void DocxAttributeOutput::WriteLineBreak()
7131 if (!m_oLineBreakClear.has_value())
7133 return;
7136 rtl::Reference<FastAttributeList> pAttr = FastSerializerHelper::createAttrList();
7137 pAttr->add(FSNS(XML_w, XML_type), "textWrapping");
7138 switch (*m_oLineBreakClear)
7140 case SwLineBreakClear::NONE:
7141 pAttr->add(FSNS(XML_w, XML_clear), "none");
7142 break;
7143 case SwLineBreakClear::LEFT:
7144 pAttr->add(FSNS(XML_w, XML_clear), "left");
7145 break;
7146 case SwLineBreakClear::RIGHT:
7147 pAttr->add(FSNS(XML_w, XML_clear), "right");
7148 break;
7149 case SwLineBreakClear::ALL:
7150 pAttr->add(FSNS(XML_w, XML_clear), "all");
7151 break;
7153 m_oLineBreakClear.reset();
7155 m_pSerializer->singleElementNS(XML_w, XML_br, pAttr);
7158 void DocxAttributeOutput::SectionLineNumbering( sal_uLong nRestartNo, const SwLineNumberInfo& rLnNumInfo )
7160 rtl::Reference<FastAttributeList> pAttr = FastSerializerHelper::createAttrList();
7161 pAttr->add( FSNS( XML_w, XML_countBy ), OString::number(rLnNumInfo.GetCountBy()));
7162 pAttr->add( FSNS( XML_w, XML_restart ), rLnNumInfo.IsRestartEachPage() ? "newPage" : "continuous" );
7163 if( rLnNumInfo.GetPosFromLeft())
7164 pAttr->add( FSNS( XML_w, XML_distance ), OString::number(rLnNumInfo.GetPosFromLeft()));
7165 if (nRestartNo > 0)
7166 // Writer is 1-based, Word is 0-based.
7167 pAttr->add(FSNS(XML_w, XML_start), OString::number(nRestartNo - 1));
7168 m_pSerializer->singleElementNS( XML_w, XML_lnNumType, pAttr );
7171 void DocxAttributeOutput::SectionTitlePage()
7173 m_pSerializer->singleElementNS(XML_w, XML_titlePg);
7176 void DocxAttributeOutput::SectionPageBorders( const SwFrameFormat* pFormat, const SwFrameFormat* /*pFirstPageFormat*/ )
7178 // Output the margins
7180 const SvxBoxItem& rBox = pFormat->GetBox( );
7182 const SvxBorderLine* pLeft = rBox.GetLeft( );
7183 const SvxBorderLine* pTop = rBox.GetTop( );
7184 const SvxBorderLine* pRight = rBox.GetRight( );
7185 const SvxBorderLine* pBottom = rBox.GetBottom( );
7187 if ( !(pBottom || pTop || pLeft || pRight) )
7188 return;
7190 OutputBorderOptions aOutputBorderOptions = lcl_getBoxBorderOptions();
7192 // Check if there is a shadow item
7193 const SfxPoolItem* pItem = GetExport().HasItem( RES_SHADOW );
7194 if ( pItem )
7196 const SvxShadowItem* pShadowItem = static_cast<const SvxShadowItem*>(pItem);
7197 aOutputBorderOptions.aShadowLocation = pShadowItem->GetLocation();
7200 // By top margin, impl_borders() means the distance between the top of the page and the header frame.
7201 editeng::WordPageMargins aMargins = m_pageMargins;
7202 HdFtDistanceGlue aGlue(pFormat->GetAttrSet());
7203 if (aGlue.HasHeader())
7204 aMargins.nTop = aGlue.m_DyaHdrTop;
7205 // Ditto for bottom margin.
7206 if (aGlue.HasFooter())
7207 aMargins.nBottom = aGlue.m_DyaHdrBottom;
7209 if (pFormat->GetDoc()->getIDocumentSettingAccess().get(DocumentSettingId::GUTTER_AT_TOP))
7211 aMargins.nTop += pFormat->GetLRSpace().GetGutterMargin();
7213 else
7215 aMargins.nLeft += pFormat->GetLRSpace().GetGutterMargin();
7218 aOutputBorderOptions.pDistances = std::make_shared<editeng::WordBorderDistances>();
7219 editeng::BorderDistancesToWord(rBox, aMargins, *aOutputBorderOptions.pDistances);
7221 // All distances are relative to the text margins
7222 m_pSerializer->startElementNS(XML_w, XML_pgBorders,
7223 FSNS(XML_w, XML_display), "allPages",
7224 FSNS(XML_w, XML_offsetFrom), aOutputBorderOptions.pDistances->bFromEdge ? "page" : "text");
7226 std::map<SvxBoxItemLine, css::table::BorderLine2> aEmptyMap; // empty styles map
7227 impl_borders( m_pSerializer, rBox, aOutputBorderOptions, aEmptyMap );
7229 m_pSerializer->endElementNS( XML_w, XML_pgBorders );
7233 void DocxAttributeOutput::SectionBiDi( bool bBiDi )
7235 if ( bBiDi )
7236 m_pSerializer->singleElementNS(XML_w, XML_bidi);
7239 // Converting Numbering Format Code to string
7240 static OString lcl_ConvertNumberingType(sal_Int16 nNumberingType, const SfxItemSet* pOutSet, OString& rFormat, const OString& sDefault = ""_ostr )
7242 OString aType = sDefault;
7244 switch ( nNumberingType )
7246 case SVX_NUM_CHARS_UPPER_LETTER:
7247 case SVX_NUM_CHARS_UPPER_LETTER_N: aType = "upperLetter"_ostr; break;
7249 case SVX_NUM_CHARS_LOWER_LETTER:
7250 case SVX_NUM_CHARS_LOWER_LETTER_N: aType = "lowerLetter"_ostr; break;
7252 case SVX_NUM_ROMAN_UPPER: aType = "upperRoman"_ostr; break;
7253 case SVX_NUM_ROMAN_LOWER: aType = "lowerRoman"_ostr; break;
7254 case SVX_NUM_ARABIC: aType = "decimal"_ostr; break;
7256 case SVX_NUM_BITMAP:
7257 case SVX_NUM_CHAR_SPECIAL: aType = "bullet"_ostr; break;
7259 case style::NumberingType::CHARS_HEBREW: aType = "hebrew2"_ostr; break;
7260 case style::NumberingType::NUMBER_HEBREW: aType = "hebrew1"_ostr; break;
7261 case style::NumberingType::NUMBER_NONE: aType = "none"_ostr; break;
7262 case style::NumberingType::FULLWIDTH_ARABIC: aType="decimalFullWidth"_ostr; break;
7263 case style::NumberingType::TIAN_GAN_ZH: aType="ideographTraditional"_ostr; break;
7264 case style::NumberingType::DI_ZI_ZH: aType="ideographZodiac"_ostr; break;
7265 case style::NumberingType::NUMBER_LOWER_ZH:
7266 aType="taiwaneseCountingThousand"_ostr;
7267 if (pOutSet) {
7268 const SvxLanguageItem& rLang = pOutSet->Get( RES_CHRATR_CJK_LANGUAGE);
7269 const LanguageType eLang = rLang.GetLanguage();
7271 if (LANGUAGE_CHINESE_SIMPLIFIED == eLang) {
7272 aType="chineseCountingThousand"_ostr;
7275 break;
7276 case style::NumberingType::NUMBER_UPPER_ZH_TW: aType="ideographLegalTraditional"_ostr;break;
7277 case style::NumberingType::NUMBER_UPPER_ZH: aType="chineseLegalSimplified"_ostr; break;
7278 case style::NumberingType::NUMBER_TRADITIONAL_JA: aType="japaneseLegal"_ostr;break;
7279 case style::NumberingType::AIU_FULLWIDTH_JA: aType="aiueoFullWidth"_ostr;break;
7280 case style::NumberingType::AIU_HALFWIDTH_JA: aType="aiueo"_ostr;break;
7281 case style::NumberingType::IROHA_FULLWIDTH_JA: aType="iroha"_ostr;break;
7282 case style::NumberingType::IROHA_HALFWIDTH_JA: aType="irohaFullWidth"_ostr;break;
7283 case style::NumberingType::HANGUL_SYLLABLE_KO: aType="ganada"_ostr;break;
7284 case style::NumberingType::HANGUL_JAMO_KO: aType="chosung"_ostr;break;
7285 case style::NumberingType::NUMBER_HANGUL_KO: aType="koreanCounting"_ostr; break;
7286 case style::NumberingType::NUMBER_LEGAL_KO: aType = "koreanLegal"_ostr; break;
7287 case style::NumberingType::NUMBER_DIGITAL_KO: aType = "koreanDigital"_ostr; break;
7288 case style::NumberingType::NUMBER_DIGITAL2_KO: aType = "koreanDigital2"_ostr; break;
7289 case style::NumberingType::CIRCLE_NUMBER: aType="decimalEnclosedCircle"_ostr; break;
7290 case style::NumberingType::CHARS_ARABIC: aType="arabicAlpha"_ostr; break;
7291 case style::NumberingType::CHARS_ARABIC_ABJAD: aType="arabicAbjad"_ostr; break;
7292 case style::NumberingType::CHARS_THAI: aType="thaiLetters"_ostr; break;
7293 case style::NumberingType::CHARS_PERSIAN:
7294 case style::NumberingType::CHARS_NEPALI: aType="hindiVowels"_ostr; break;
7295 case style::NumberingType::CHARS_CYRILLIC_UPPER_LETTER_RU:
7296 case style::NumberingType::CHARS_CYRILLIC_UPPER_LETTER_N_RU: aType = "russianUpper"_ostr; break;
7297 case style::NumberingType::CHARS_CYRILLIC_LOWER_LETTER_RU:
7298 case style::NumberingType::CHARS_CYRILLIC_LOWER_LETTER_N_RU: aType = "russianLower"_ostr; break;
7299 case style::NumberingType::TEXT_NUMBER: aType="ordinal"_ostr; break;
7300 case style::NumberingType::TEXT_CARDINAL: aType="cardinalText"_ostr; break;
7301 case style::NumberingType::TEXT_ORDINAL: aType="ordinalText"_ostr; break;
7302 case style::NumberingType::SYMBOL_CHICAGO: aType="chicago"_ostr; break;
7303 case style::NumberingType::ARABIC_ZERO: aType = "decimalZero"_ostr; break;
7304 case style::NumberingType::ARABIC_ZERO3:
7305 aType = "custom"_ostr;
7306 rFormat = "001, 002, 003, ..."_ostr;
7307 break;
7308 case style::NumberingType::ARABIC_ZERO4:
7309 aType = "custom"_ostr;
7310 rFormat = "0001, 0002, 0003, ..."_ostr;
7311 break;
7312 case style::NumberingType::ARABIC_ZERO5:
7313 aType = "custom"_ostr;
7314 rFormat = "00001, 00002, 00003, ..."_ostr;
7315 break;
7317 Fallback the rest to the suggested default.
7318 case style::NumberingType::NATIVE_NUMBERING:
7319 case style::NumberingType::HANGUL_CIRCLED_JAMO_KO:
7320 case style::NumberingType::HANGUL_CIRCLED_SYLLABLE_KO:
7321 case style::NumberingType::CHARS_GREEK_UPPER_LETTER:
7322 case style::NumberingType::CHARS_GREEK_LOWER_LETTER:
7323 case style::NumberingType::PAGE_DESCRIPTOR:
7324 case style::NumberingType::TRANSLITERATION:
7325 case style::NumberingType::CHARS_KHMER:
7326 case style::NumberingType::CHARS_LAO:
7327 case style::NumberingType::CHARS_TIBETAN:
7328 case style::NumberingType::CHARS_CYRILLIC_UPPER_LETTER_BG:
7329 case style::NumberingType::CHARS_CYRILLIC_LOWER_LETTER_BG:
7330 case style::NumberingType::CHARS_CYRILLIC_UPPER_LETTER_N_BG:
7331 case style::NumberingType::CHARS_CYRILLIC_LOWER_LETTER_N_BG:
7332 case style::NumberingType::CHARS_MYANMAR:
7333 case style::NumberingType::CHARS_CYRILLIC_UPPER_LETTER_SR:
7334 case style::NumberingType::CHARS_CYRILLIC_LOWER_LETTER_SR:
7335 case style::NumberingType::CHARS_CYRILLIC_UPPER_LETTER_N_SR:
7336 case style::NumberingType::CHARS_CYRILLIC_LOWER_LETTER_N_SR:
7337 case style::NumberingType::CHARS_CYRILLIC_UPPER_LETTER_UK:
7338 case style::NumberingType::CHARS_CYRILLIC_LOWER_LETTER_UK:
7339 case style::NumberingType::CHARS_CYRILLIC_UPPER_LETTER_N_UK:
7340 case style::NumberingType::CHARS_CYRILLIC_LOWER_LETTER_N_UK:
7342 default: break;
7344 return aType;
7348 void DocxAttributeOutput::SectionPageNumbering( sal_uInt16 nNumType, const ::std::optional<sal_uInt16>& oPageRestartNumber )
7350 // FIXME Not called properly with page styles like "First Page"
7352 rtl::Reference<FastAttributeList> pAttr = FastSerializerHelper::createAttrList();
7354 // std::nullopt means no restart: then don't output that attribute if it is negative
7355 if ( oPageRestartNumber )
7356 pAttr->add( FSNS( XML_w, XML_start ), OString::number( *oPageRestartNumber ) );
7358 // nNumType corresponds to w:fmt. See WW8Export::GetNumId() for more precisions
7359 OString aCustomFormat;
7360 OString aFormat(lcl_ConvertNumberingType(nNumType, nullptr, aCustomFormat));
7361 if (!aFormat.isEmpty() && aCustomFormat.isEmpty())
7362 pAttr->add(FSNS(XML_w, XML_fmt), aFormat);
7364 m_pSerializer->singleElementNS( XML_w, XML_pgNumType, pAttr );
7366 // see 2.6.12 pgNumType (Page Numbering Settings)
7367 SAL_INFO("sw.ww8", "TODO DocxAttributeOutput::SectionPageNumbering()" );
7370 void DocxAttributeOutput::SectionType( sal_uInt8 nBreakCode )
7372 /* break code: 0 No break, 1 New column
7373 2 New page, 3 Even page, 4 Odd page
7375 const char* pType;
7376 switch ( nBreakCode )
7378 case 1: pType = "nextColumn"; break;
7379 case 2: pType = "nextPage"; break;
7380 case 3: pType = "evenPage"; break;
7381 case 4: pType = "oddPage"; break;
7382 default: pType = "continuous"; break;
7385 m_pSerializer->singleElementNS(XML_w, XML_type, FSNS(XML_w, XML_val), pType);
7388 void DocxAttributeOutput::TextVerticalAdjustment( const drawing::TextVerticalAdjust nVA )
7390 switch( nVA )
7392 case drawing::TextVerticalAdjust_CENTER:
7393 m_pSerializer->singleElementNS(XML_w, XML_vAlign, FSNS(XML_w, XML_val), "center");
7394 break;
7395 case drawing::TextVerticalAdjust_BOTTOM:
7396 m_pSerializer->singleElementNS(XML_w, XML_vAlign, FSNS(XML_w, XML_val), "bottom");
7397 break;
7398 case drawing::TextVerticalAdjust_BLOCK: //justify
7399 m_pSerializer->singleElementNS(XML_w, XML_vAlign, FSNS(XML_w, XML_val), "both");
7400 break;
7401 default:
7402 break;
7406 void DocxAttributeOutput::StartFont( const OUString& rFamilyName ) const
7408 m_pSerializer->startElementNS(XML_w, XML_font, FSNS(XML_w, XML_name), rFamilyName);
7411 void DocxAttributeOutput::EndFont() const
7413 m_pSerializer->endElementNS( XML_w, XML_font );
7416 void DocxAttributeOutput::FontAlternateName( const OUString& rName ) const
7418 m_pSerializer->singleElementNS(XML_w, XML_altName, FSNS(XML_w, XML_val), rName);
7421 void DocxAttributeOutput::FontCharset( sal_uInt8 nCharSet, rtl_TextEncoding nEncoding ) const
7423 rtl::Reference<FastAttributeList> pAttr = FastSerializerHelper::createAttrList();
7425 OString aCharSet( OString::number( nCharSet, 16 ) );
7426 if ( aCharSet.getLength() == 1 )
7427 aCharSet = "0" + aCharSet;
7428 pAttr->add(FSNS(XML_w, XML_val), aCharSet);
7430 if (GetExport().GetFilter().getVersion() != oox::core::ECMA_376_1ST_EDITION)
7432 if( const char* charset = rtl_getMimeCharsetFromTextEncoding( nEncoding ))
7433 pAttr->add( FSNS( XML_w, XML_characterSet ), charset );
7436 m_pSerializer->singleElementNS( XML_w, XML_charset, pAttr );
7439 void DocxAttributeOutput::FontFamilyType( FontFamily eFamily ) const
7441 const char* pFamily;
7442 switch ( eFamily )
7444 case FAMILY_ROMAN: pFamily = "roman"; break;
7445 case FAMILY_SWISS: pFamily = "swiss"; break;
7446 case FAMILY_MODERN: pFamily = "modern"; break;
7447 case FAMILY_SCRIPT: pFamily = "script"; break;
7448 case FAMILY_DECORATIVE: pFamily = "decorative"; break;
7449 default: pFamily = "auto"; break; // no font family
7452 m_pSerializer->singleElementNS(XML_w, XML_family, FSNS(XML_w, XML_val), pFamily);
7455 void DocxAttributeOutput::FontPitchType( FontPitch ePitch ) const
7457 const char* pPitch;
7458 switch ( ePitch )
7460 case PITCH_VARIABLE: pPitch = "variable"; break;
7461 case PITCH_FIXED: pPitch = "fixed"; break;
7462 default: pPitch = "default"; break; // no info about the pitch
7465 m_pSerializer->singleElementNS(XML_w, XML_pitch, FSNS(XML_w, XML_val), pPitch);
7468 void DocxAttributeOutput::EmbedFont( std::u16string_view name, FontFamily family, FontPitch pitch )
7470 if( !m_rExport.m_rDoc.getIDocumentSettingAccess().get( DocumentSettingId::EMBED_FONTS ))
7471 return; // no font embedding with this document
7472 bool foundFont
7473 = EmbedFontStyle(name, XML_embedRegular, family, ITALIC_NONE, WEIGHT_NORMAL, pitch);
7474 foundFont
7475 = EmbedFontStyle(name, XML_embedBold, family, ITALIC_NONE, WEIGHT_BOLD, pitch) || foundFont;
7476 foundFont = EmbedFontStyle(name, XML_embedItalic, family, ITALIC_NORMAL, WEIGHT_NORMAL, pitch)
7477 || foundFont;
7478 foundFont = EmbedFontStyle(name, XML_embedBoldItalic, family, ITALIC_NORMAL, WEIGHT_BOLD, pitch)
7479 || foundFont;
7480 if (!foundFont)
7481 EmbedFontStyle(name, XML_embedRegular, family, ITALIC_NONE, WEIGHT_DONTKNOW, pitch);
7484 static char toHexChar( int value )
7486 return value >= 10 ? value + 'A' - 10 : value + '0';
7489 bool DocxAttributeOutput::EmbedFontStyle(std::u16string_view name, int tag, FontFamily family,
7490 FontItalic italic, FontWeight weight, FontPitch pitch)
7492 // Embed font if at least viewing is allowed (in which case the opening app must check
7493 // the font license rights too and open either read-only or not use the font for editing).
7494 OUString fontUrl = EmbeddedFontsHelper::fontFileUrl( name, family, italic, weight, pitch,
7495 EmbeddedFontsHelper::FontRights::ViewingAllowed );
7496 if( fontUrl.isEmpty())
7497 return false;
7498 // TODO IDocumentSettingAccess::EMBED_SYSTEM_FONTS
7499 if( !m_FontFilesMap.count( fontUrl ))
7501 osl::File file( fontUrl );
7502 if( file.open( osl_File_OpenFlag_Read ) != osl::File::E_None )
7503 return false;
7504 uno::Reference< css::io::XOutputStream > xOutStream = m_rExport.GetFilter().openFragmentStream(
7505 "word/fonts/font" + OUString::number(m_nextFontId) + ".odttf",
7506 u"application/vnd.openxmlformats-officedocument.obfuscatedFont"_ustr );
7507 // Not much point in trying hard with the obfuscation key, whoever reads the spec can read the font anyway,
7508 // so just alter the first and last part of the key.
7509 char fontKeyStr[] = "{00014A78-CABC-4EF0-12AC-5CD89AEFDE00}";
7510 sal_uInt8 fontKey[ 16 ] = { 0, 0xDE, 0xEF, 0x9A, 0xD8, 0x5C, 0xAC, 0x12, 0xF0, 0x4E,
7511 0xBC, 0xCA, 0x78, 0x4A, 0x01, 0 };
7512 fontKey[ 0 ] = fontKey[ 15 ] = m_nextFontId % 256;
7513 fontKeyStr[ 1 ] = fontKeyStr[ 35 ] = toHexChar(( m_nextFontId % 256 ) / 16 );
7514 fontKeyStr[ 2 ] = fontKeyStr[ 36 ] = toHexChar(( m_nextFontId % 256 ) % 16 );
7515 unsigned char buffer[ 4096 ];
7516 sal_uInt64 readSize;
7517 file.read( buffer, 32, readSize );
7518 if( readSize < 32 )
7520 SAL_WARN( "sw.ww8", "Font file size too small (" << fontUrl << ")" );
7521 xOutStream->closeOutput();
7522 return false;
7524 for( int i = 0;
7525 i < 16;
7526 ++i )
7528 buffer[ i ] ^= fontKey[ i ];
7529 buffer[ i + 16 ] ^= fontKey[ i ];
7531 xOutStream->writeBytes( uno::Sequence< sal_Int8 >( reinterpret_cast< const sal_Int8* >( buffer ), 32 ));
7532 for(;;)
7534 sal_Bool eof;
7535 if( file.isEndOfFile( &eof ) != osl::File::E_None )
7537 SAL_WARN( "sw.ww8", "Error reading font file " << fontUrl );
7538 xOutStream->closeOutput();
7539 return false;
7541 if( eof )
7542 break;
7543 if( file.read( buffer, 4096, readSize ) != osl::File::E_None )
7545 SAL_WARN( "sw.ww8", "Error reading font file " << fontUrl );
7546 xOutStream->closeOutput();
7547 return false;
7549 if( readSize == 0 )
7550 break;
7551 // coverity[overrun-buffer-arg : FALSE] - coverity has difficulty with css::uno::Sequence
7552 xOutStream->writeBytes( uno::Sequence< sal_Int8 >( reinterpret_cast< const sal_Int8* >( buffer ), readSize ));
7554 xOutStream->closeOutput();
7555 EmbeddedFontRef ref;
7556 ref.relId = OUStringToOString( GetExport().GetFilter().addRelation( m_pSerializer->getOutputStream(),
7557 oox::getRelationship(Relationship::FONT),
7558 Concat2View("fonts/font" + OUString::number( m_nextFontId ) + ".odttf") ), RTL_TEXTENCODING_UTF8 );
7559 ref.fontKey = fontKeyStr;
7560 m_FontFilesMap[ fontUrl ] = std::move(ref);
7561 ++m_nextFontId;
7563 m_pSerializer->singleElementNS( XML_w, tag,
7564 FSNS( XML_r, XML_id ), m_FontFilesMap[ fontUrl ].relId,
7565 FSNS( XML_w, XML_fontKey ), m_FontFilesMap[ fontUrl ].fontKey );
7566 return true;
7569 OString DocxAttributeOutput::TransHighlightColor( sal_uInt8 nIco )
7571 switch (nIco)
7573 case 0: return "none"_ostr; break;
7574 case 1: return "black"_ostr; break;
7575 case 2: return "blue"_ostr; break;
7576 case 3: return "cyan"_ostr; break;
7577 case 4: return "green"_ostr; break;
7578 case 5: return "magenta"_ostr; break;
7579 case 6: return "red"_ostr; break;
7580 case 7: return "yellow"_ostr; break;
7581 case 8: return "white"_ostr; break;
7582 case 9: return "darkBlue"_ostr; break;
7583 case 10: return "darkCyan"_ostr; break;
7584 case 11: return "darkGreen"_ostr; break;
7585 case 12: return "darkMagenta"_ostr; break;
7586 case 13: return "darkRed"_ostr; break;
7587 case 14: return "darkYellow"_ostr; break;
7588 case 15: return "darkGray"_ostr; break;
7589 case 16: return "lightGray"_ostr; break;
7590 default: return OString(); break;
7594 void DocxAttributeOutput::NumberingDefinition( sal_uInt16 nId, const SwNumRule &rRule )
7596 // nId is the same both for abstract numbering definition as well as the
7597 // numbering definition itself
7598 // TODO check that this is actually true & fix if not ;-)
7599 OString aId( OString::number( nId ) );
7601 m_pSerializer->startElementNS(XML_w, XML_num, FSNS(XML_w, XML_numId), aId);
7603 m_pSerializer->singleElementNS(XML_w, XML_abstractNumId, FSNS(XML_w, XML_val), aId);
7605 #if OSL_DEBUG_LEVEL > 1
7606 // TODO ww8 version writes this, anything to do about it here?
7607 if ( rRule.IsContinusNum() )
7608 SAL_INFO("sw", "TODO DocxAttributeOutput::NumberingDefinition()" );
7609 #else
7610 (void) rRule; // to quiet the warning...
7611 #endif
7613 m_pSerializer->endElementNS( XML_w, XML_num );
7616 // Not all attributes of SwNumFormat are important for export, so can't just use embedded in
7617 // that classes comparison.
7618 static bool lcl_ListLevelsAreDifferentForExport(const SwNumFormat & rFormat1, const SwNumFormat & rFormat2)
7620 if (rFormat1 == rFormat2)
7621 // They are equal, nothing to do
7622 return false;
7624 if (!rFormat1.GetCharFormat() != !rFormat2.GetCharFormat())
7625 // One has charformat, other not. they are different
7626 return true;
7628 if (rFormat1.GetCharFormat() && rFormat2.GetCharFormat())
7630 const SwAttrSet & a1 = rFormat1.GetCharFormat()->GetAttrSet();
7631 const SwAttrSet & a2 = rFormat2.GetCharFormat()->GetAttrSet();
7633 if (!(a1 == a2))
7634 // Difference in charformat: they are different
7635 return true;
7638 // Compare numformats with empty charformats
7639 SwNumFormat modified1 = rFormat1;
7640 SwNumFormat modified2 = rFormat2;
7641 modified1.SetCharFormatName(OUString());
7642 modified2.SetCharFormatName(OUString());
7643 modified1.SetCharFormat(nullptr);
7644 modified2.SetCharFormat(nullptr);
7645 return modified1 != modified2;
7648 void DocxAttributeOutput::OverrideNumberingDefinition(
7649 SwNumRule const& rRule,
7650 sal_uInt16 const nNum, sal_uInt16 const nAbstractNum, const std::map< size_t, size_t > & rLevelOverrides )
7652 m_pSerializer->startElementNS(XML_w, XML_num, FSNS(XML_w, XML_numId), OString::number(nNum));
7654 m_pSerializer->singleElementNS(XML_w, XML_abstractNumId, FSNS(XML_w, XML_val), OString::number(nAbstractNum));
7656 SwNumRule const& rAbstractRule = *(*m_rExport.m_pUsedNumTable)[nAbstractNum - 1];
7657 sal_uInt8 const nLevels = static_cast<sal_uInt8>(rRule.IsContinusNum()
7658 ? WW8ListManager::nMinLevel : WW8ListManager::nMaxLevel);
7659 sal_uInt8 nPreviousOverrideLevel = 0;
7660 for (sal_uInt8 nLevel = 0; nLevel < nLevels; ++nLevel)
7662 const auto levelOverride = rLevelOverrides.find(nLevel);
7663 bool bListsAreDifferent = lcl_ListLevelsAreDifferentForExport(rRule.Get(nLevel), rAbstractRule.Get(nLevel));
7665 // Export list override only if it is different to abstract one
7666 // or we have a level numbering override
7667 if (bListsAreDifferent || levelOverride != rLevelOverrides.end())
7669 // If there are "gaps" in w:lvlOverride numbers, MS Word can have issues with numbering.
7670 // So we need to emit default override tokens up to current one.
7671 while (nPreviousOverrideLevel < nLevel)
7673 const SwNumFormat& rFormat = rRule.Get(nPreviousOverrideLevel);
7674 m_pSerializer->startElementNS(XML_w, XML_lvlOverride, FSNS(XML_w, XML_ilvl), OString::number(nPreviousOverrideLevel));
7675 // tdf#153104: absent startOverride is treated by Word as "startOverride value 0".
7676 m_pSerializer->singleElementNS(XML_w, XML_startOverride, FSNS(XML_w, XML_val), OString::number(rFormat.GetStart()));
7677 m_pSerializer->endElementNS(XML_w, XML_lvlOverride);
7678 nPreviousOverrideLevel++;
7681 m_pSerializer->startElementNS(XML_w, XML_lvlOverride, FSNS(XML_w, XML_ilvl), OString::number(nLevel));
7683 if (bListsAreDifferent)
7685 GetExport().NumberingLevel(rRule, nLevel);
7687 if (levelOverride != rLevelOverrides.end())
7689 // list numbering restart override
7690 m_pSerializer->singleElementNS(XML_w, XML_startOverride,
7691 FSNS(XML_w, XML_val), OString::number(levelOverride->second));
7694 m_pSerializer->endElementNS(XML_w, XML_lvlOverride);
7698 m_pSerializer->endElementNS( XML_w, XML_num );
7701 void DocxAttributeOutput::StartAbstractNumbering( sal_uInt16 nId )
7703 const SwNumRule* pRule = (*m_rExport.m_pUsedNumTable)[nId - 1];
7704 m_bExportingOutline = pRule && pRule->IsOutlineRule();
7705 m_pSerializer->startElementNS( XML_w, XML_abstractNum,
7706 FSNS( XML_w, XML_abstractNumId ), OString::number(nId) );
7709 void DocxAttributeOutput::EndAbstractNumbering()
7711 m_pSerializer->endElementNS( XML_w, XML_abstractNum );
7714 void DocxAttributeOutput::NumberingLevel( sal_uInt8 nLevel,
7715 sal_uInt16 nStart,
7716 sal_uInt16 nNumberingType,
7717 SvxAdjust eAdjust,
7718 const sal_uInt8 * /*pNumLvlPos*/,
7719 sal_uInt8 nFollow,
7720 const wwFont *pFont,
7721 const SfxItemSet *pOutSet,
7722 sal_Int16 nIndentAt,
7723 sal_Int16 nFirstLineIndex,
7724 sal_Int16 nListTabPos,
7725 const OUString &rNumberingString,
7726 const SvxBrushItem* pBrush,
7727 bool isLegal)
7729 m_pSerializer->startElementNS(XML_w, XML_lvl, FSNS(XML_w, XML_ilvl), OString::number(nLevel));
7731 // start with the nStart value. Do not write w:start if Numbered Lists
7732 // starts from zero.As it's an optional parameter.
7733 // refer ECMA 376 Second edition Part-1
7734 if(0 != nLevel || 0 != nStart)
7736 m_pSerializer->singleElementNS( XML_w, XML_start,
7737 FSNS( XML_w, XML_val ), OString::number(nStart) );
7740 if (m_bExportingOutline)
7742 sal_uInt16 nId = m_rExport.m_pStyles->GetHeadingParagraphStyleId( nLevel );
7743 if ( nId != SAL_MAX_UINT16 )
7744 m_pSerializer->singleElementNS( XML_w, XML_pStyle ,
7745 FSNS( XML_w, XML_val ), m_rExport.m_pStyles->GetStyleId(nId) );
7748 if (isLegal)
7749 m_pSerializer->singleElementNS(XML_w, XML_isLgl);
7751 // format
7752 OString aCustomFormat;
7753 OString aFormat(lcl_ConvertNumberingType(nNumberingType, pOutSet, aCustomFormat, "decimal"_ostr));
7756 if (aCustomFormat.isEmpty())
7758 m_pSerializer->singleElementNS(XML_w, XML_numFmt, FSNS(XML_w, XML_val), aFormat);
7760 else
7762 m_pSerializer->startElementNS(XML_mc, XML_AlternateContent);
7763 m_pSerializer->startElementNS(XML_mc, XML_Choice, XML_Requires, "w14");
7765 m_pSerializer->singleElementNS(XML_w, XML_numFmt, FSNS(XML_w, XML_val), aFormat,
7766 FSNS(XML_w, XML_format), aCustomFormat);
7768 m_pSerializer->endElementNS(XML_mc, XML_Choice);
7769 m_pSerializer->startElementNS(XML_mc, XML_Fallback);
7770 m_pSerializer->singleElementNS(XML_w, XML_numFmt, FSNS(XML_w, XML_val), "decimal");
7771 m_pSerializer->endElementNS(XML_mc, XML_Fallback);
7772 m_pSerializer->endElementNS(XML_mc, XML_AlternateContent);
7776 // suffix
7777 const char *pSuffix = nullptr;
7778 switch ( nFollow )
7780 case 1: pSuffix = "space"; break;
7781 case 2: pSuffix = "nothing"; break;
7782 default: /*pSuffix = "tab";*/ break;
7784 if ( pSuffix )
7785 m_pSerializer->singleElementNS(XML_w, XML_suff, FSNS(XML_w, XML_val), pSuffix);
7787 // text
7788 OUStringBuffer aBuffer( rNumberingString.getLength() + WW8ListManager::nMaxLevel );
7790 const sal_Unicode *pPrev = rNumberingString.getStr();
7791 const sal_Unicode *pIt = rNumberingString.getStr();
7792 while ( pIt < rNumberingString.getStr() + rNumberingString.getLength() )
7794 // convert the level values to %NUMBER form
7795 // (we don't use pNumLvlPos at all)
7796 // FIXME so far we support the ww8 limit of levels only
7797 if ( *pIt < sal_Unicode( WW8ListManager::nMaxLevel ) )
7799 aBuffer.append( OUString::Concat(std::u16string_view(pPrev, pIt - pPrev))
7800 + "%"
7801 + OUString::number(sal_Int32( *pIt ) + 1 ));
7803 pPrev = pIt + 1;
7805 ++pIt;
7807 if ( pPrev < pIt )
7808 aBuffer.append( pPrev, pIt - pPrev );
7810 // If bullet char is empty, set lvlText as empty
7811 if ( rNumberingString == OUStringChar('\0') && nNumberingType == SVX_NUM_CHAR_SPECIAL )
7813 m_pSerializer->singleElementNS(XML_w, XML_lvlText, FSNS(XML_w, XML_val), "");
7815 else
7817 // Writer's "zero width space" suffix is necessary, so that LabelFollowedBy shows up, but Word doesn't require that.
7818 OUString aLevelText = aBuffer.makeStringAndClear();
7819 static OUString aZeroWidthSpace(u'\x200B');
7820 if (aLevelText == aZeroWidthSpace)
7821 aLevelText.clear();
7822 m_pSerializer->singleElementNS(XML_w, XML_lvlText, FSNS(XML_w, XML_val), aLevelText);
7825 // bullet
7826 if (nNumberingType == SVX_NUM_BITMAP && pBrush)
7828 int nIndex = m_rExport.GetGrfIndex(*pBrush);
7829 if (nIndex != -1)
7831 m_pSerializer->singleElementNS(XML_w, XML_lvlPicBulletId,
7832 FSNS(XML_w, XML_val), OString::number(nIndex));
7836 // justification
7837 const char *pJc;
7838 bool const ecmaDialect = m_rExport.GetFilter().getVersion() == oox::core::ECMA_376_1ST_EDITION;
7839 switch ( eAdjust )
7841 case SvxAdjust::Center: pJc = "center"; break;
7842 case SvxAdjust::Right: pJc = !ecmaDialect ? "end" : "right"; break;
7843 default: pJc = !ecmaDialect ? "start" : "left"; break;
7845 m_pSerializer->singleElementNS(XML_w, XML_lvlJc, FSNS(XML_w, XML_val), pJc);
7847 // indentation
7848 m_pSerializer->startElementNS(XML_w, XML_pPr);
7849 if( nListTabPos >= 0 )
7851 m_pSerializer->startElementNS(XML_w, XML_tabs);
7852 m_pSerializer->singleElementNS( XML_w, XML_tab,
7853 FSNS( XML_w, XML_val ), "num",
7854 FSNS( XML_w, XML_pos ), OString::number(nListTabPos) );
7855 m_pSerializer->endElementNS( XML_w, XML_tabs );
7858 sal_Int32 nToken = ecmaDialect ? XML_left : XML_start;
7859 sal_Int32 nIndentToken = nFirstLineIndex > 0 ? XML_firstLine : XML_hanging;
7860 m_pSerializer->singleElementNS( XML_w, XML_ind,
7861 FSNS( XML_w, nToken ), OString::number(nIndentAt),
7862 FSNS( XML_w, nIndentToken ), OString::number(abs(nFirstLineIndex)) );
7863 m_pSerializer->endElementNS( XML_w, XML_pPr );
7865 // font
7866 if ( pOutSet )
7868 m_pSerializer->startElementNS(XML_w, XML_rPr);
7870 SfxItemSet aTempSet(*pOutSet);
7871 if ( pFont )
7873 GetExport().GetId( *pFont ); // ensure font info is written to fontTable.xml
7874 OString aFamilyName( OUStringToOString( pFont->GetFamilyName(), RTL_TEXTENCODING_UTF8 ) );
7875 m_pSerializer->singleElementNS( XML_w, XML_rFonts,
7876 FSNS( XML_w, XML_ascii ), aFamilyName,
7877 FSNS( XML_w, XML_hAnsi ), aFamilyName,
7878 FSNS( XML_w, XML_cs ), aFamilyName,
7879 FSNS( XML_w, XML_hint ), "default" );
7880 aTempSet.ClearItem(RES_CHRATR_FONT);
7881 aTempSet.ClearItem(RES_CHRATR_CTL_FONT);
7883 m_rExport.OutputItemSet(aTempSet, false, true, i18n::ScriptType::LATIN, m_rExport.m_bExportModeRTF);
7885 WriteCollectedRunProperties();
7887 m_pSerializer->endElementNS( XML_w, XML_rPr );
7890 // TODO anything to do about nListTabPos?
7892 m_pSerializer->endElementNS( XML_w, XML_lvl );
7895 void DocxAttributeOutput::CharCaseMap( const SvxCaseMapItem& rCaseMap )
7897 switch ( rCaseMap.GetValue() )
7899 case SvxCaseMap::SmallCaps:
7900 m_pSerializer->singleElementNS(XML_w, XML_smallCaps);
7901 break;
7902 case SvxCaseMap::Uppercase:
7903 m_pSerializer->singleElementNS(XML_w, XML_caps);
7904 break;
7905 default: // Something that ooxml does not support
7906 m_pSerializer->singleElementNS(XML_w, XML_smallCaps, FSNS(XML_w, XML_val), "false");
7907 m_pSerializer->singleElementNS(XML_w, XML_caps, FSNS(XML_w, XML_val), "false");
7908 break;
7912 void DocxAttributeOutput::CharColor(const SvxColorItem& rColorItem)
7914 const Color aColor = rColorItem.getColor();
7915 const model::ComplexColor& aComplexColor = rColorItem.getComplexColor();
7917 OString aColorString = msfilter::util::ConvertColor(aColor);
7919 std::string_view pExistingValue;
7920 if (m_pColorAttrList.is() && m_pColorAttrList->getAsView(FSNS(XML_w, XML_val), pExistingValue))
7922 assert(aColorString.equalsL(pExistingValue.data(), pExistingValue.size()));
7923 return;
7926 lclAddThemeColorAttributes(m_pColorAttrList, aComplexColor);
7928 AddToAttrList(m_pColorAttrList, FSNS(XML_w, XML_val), aColorString);
7929 m_nCharTransparence = 255 - aColor.GetAlpha();
7930 m_aComplexColor = aComplexColor;
7933 void DocxAttributeOutput::CharContour( const SvxContourItem& rContour )
7935 if ( rContour.GetValue() )
7936 m_pSerializer->singleElementNS(XML_w, XML_outline);
7937 else
7938 m_pSerializer->singleElementNS(XML_w, XML_outline, FSNS(XML_w, XML_val), "false");
7941 void DocxAttributeOutput::CharCrossedOut( const SvxCrossedOutItem& rCrossedOut )
7943 switch ( rCrossedOut.GetStrikeout() )
7945 case STRIKEOUT_DOUBLE:
7946 m_pSerializer->singleElementNS(XML_w, XML_dstrike);
7947 break;
7948 case STRIKEOUT_NONE:
7949 m_pSerializer->singleElementNS(XML_w, XML_dstrike, FSNS(XML_w, XML_val), "false");
7950 m_pSerializer->singleElementNS(XML_w, XML_strike, FSNS(XML_w, XML_val), "false");
7951 break;
7952 default:
7953 m_pSerializer->singleElementNS(XML_w, XML_strike);
7954 break;
7958 void DocxAttributeOutput::CharEscapement( const SvxEscapementItem& rEscapement )
7960 OString sIss;
7961 short nEsc = rEscapement.GetEsc(), nProp = rEscapement.GetProportionalHeight();
7963 bool bParaStyle = false;
7964 if (m_rExport.m_bStyDef && m_rExport.m_pCurrentStyle)
7966 bParaStyle = m_rExport.m_pCurrentStyle->Which() == RES_TXTFMTCOLL;
7969 // Simplify styles to avoid impossible complexity. Import and export as defaults only
7970 if ( m_rExport.m_bStyDef && nEsc && !(bParaStyle && nEsc < 0))
7972 nProp = DFLT_ESC_PROP;
7973 nEsc = (nEsc > 0) ? DFLT_ESC_AUTO_SUPER : DFLT_ESC_AUTO_SUB;
7976 if ( !nEsc )
7978 sIss = "baseline"_ostr;
7979 nEsc = 0;
7980 nProp = 100;
7982 else if ( DFLT_ESC_PROP == nProp || nProp < 1 || nProp > 100 )
7984 if ( DFLT_ESC_SUB == nEsc || DFLT_ESC_AUTO_SUB == nEsc )
7985 sIss = "subscript"_ostr;
7986 else if ( DFLT_ESC_SUPER == nEsc || DFLT_ESC_AUTO_SUPER == nEsc )
7987 sIss = "superscript"_ostr;
7989 else if ( DFLT_ESC_AUTO_SUPER == nEsc )
7991 // Raised by the differences between the ascenders (ascent = baseline to top of highest letter).
7992 // The ascent is generally about 80% of the total font height.
7993 // That is why DFLT_ESC_PROP (58) leads to 33% (DFLT_ESC_SUPER)
7994 nEsc = .8 * (100 - nProp);
7996 else if ( DFLT_ESC_AUTO_SUB == nEsc )
7998 // Lowered by the differences between the descenders (descent = baseline to bottom of lowest letter).
7999 // The descent is generally about 20% of the total font height.
8000 // That is why DFLT_ESC_PROP (58) leads to 8% (DFLT_ESC_SUB)
8001 nEsc = .2 * -(100 - nProp);
8004 if ( !sIss.isEmpty() )
8005 m_pSerializer->singleElementNS(XML_w, XML_vertAlign, FSNS(XML_w, XML_val), sIss);
8007 if (!(sIss.isEmpty() || sIss.match("baseline")))
8008 return;
8010 const SvxFontHeightItem& rItem = m_rExport.GetItem(RES_CHRATR_FONTSIZE);
8011 float fHeight = rItem.GetHeight();
8012 OString sPos = OString::number( round(( fHeight * nEsc ) / 1000) );
8013 m_pSerializer->singleElementNS(XML_w, XML_position, FSNS(XML_w, XML_val), sPos);
8015 if( ( 100 != nProp || sIss.match( "baseline" ) ) && !m_rExport.m_bFontSizeWritten )
8017 OString sSize = OString::number( round(( fHeight * nProp ) / 1000) );
8018 m_pSerializer->singleElementNS(XML_w, XML_sz, FSNS(XML_w, XML_val), sSize);
8022 void DocxAttributeOutput::CharFont( const SvxFontItem& rFont)
8024 GetExport().GetId( rFont ); // ensure font info is written to fontTable.xml
8025 const OUString& sFontName(rFont.GetFamilyName());
8026 if (sFontName.isEmpty())
8027 return;
8029 if (m_pFontsAttrList &&
8030 ( m_pFontsAttrList->hasAttribute(FSNS( XML_w, XML_ascii )) ||
8031 m_pFontsAttrList->hasAttribute(FSNS( XML_w, XML_hAnsi )) )
8034 // tdf#38778: do to fields output into DOC the font could be added before and after field declaration
8035 // that all sub runs of the field will have correct font inside.
8036 // For DOCX we should do not add the same font information twice in the same node
8037 return;
8040 AddToAttrList( m_pFontsAttrList,
8041 FSNS( XML_w, XML_ascii ), sFontName,
8042 FSNS( XML_w, XML_hAnsi ), sFontName );
8045 void DocxAttributeOutput::CharFontSize( const SvxFontHeightItem& rFontSize)
8047 OString fontSize = OString::number( ( rFontSize.GetHeight() + 5 ) / 10 );
8049 switch ( rFontSize.Which() )
8051 case RES_CHRATR_FONTSIZE:
8052 case RES_CHRATR_CJK_FONTSIZE:
8053 m_pSerializer->singleElementNS(XML_w, XML_sz, FSNS(XML_w, XML_val), fontSize);
8054 break;
8055 case RES_CHRATR_CTL_FONTSIZE:
8056 m_pSerializer->singleElementNS(XML_w, XML_szCs, FSNS(XML_w, XML_val), fontSize);
8057 break;
8061 void DocxAttributeOutput::CharKerning( const SvxKerningItem& rKerning )
8063 OString aKerning = OString::number( rKerning.GetValue() );
8064 m_pSerializer->singleElementNS(XML_w, XML_spacing, FSNS(XML_w, XML_val), aKerning);
8067 void DocxAttributeOutput::CharLanguage( const SvxLanguageItem& rLanguage )
8069 OUString aLanguageCode(LanguageTag( rLanguage.GetLanguage()).getBcp47MS());
8071 switch ( rLanguage.Which() )
8073 case RES_CHRATR_LANGUAGE:
8074 AddToAttrList( m_pCharLangAttrList, FSNS( XML_w, XML_val ), aLanguageCode );
8075 break;
8076 case RES_CHRATR_CJK_LANGUAGE:
8077 AddToAttrList( m_pCharLangAttrList, FSNS( XML_w, XML_eastAsia ), aLanguageCode );
8078 break;
8079 case RES_CHRATR_CTL_LANGUAGE:
8080 AddToAttrList( m_pCharLangAttrList, FSNS( XML_w, XML_bidi ), aLanguageCode );
8081 break;
8085 void DocxAttributeOutput::CharPosture( const SvxPostureItem& rPosture )
8087 if ( rPosture.GetPosture() != ITALIC_NONE )
8088 m_pSerializer->singleElementNS(XML_w, XML_i);
8089 else
8090 m_pSerializer->singleElementNS(XML_w, XML_i, FSNS(XML_w, XML_val), "false");
8093 void DocxAttributeOutput::CharShadow( const SvxShadowedItem& rShadow )
8095 if ( rShadow.GetValue() )
8096 m_pSerializer->singleElementNS(XML_w, XML_shadow);
8097 else
8098 m_pSerializer->singleElementNS(XML_w, XML_shadow, FSNS(XML_w, XML_val), "false");
8101 void DocxAttributeOutput::CharUnderline( const SvxUnderlineItem& rUnderline )
8103 const char *pUnderlineValue;
8105 switch ( rUnderline.GetLineStyle() )
8107 case LINESTYLE_SINGLE: pUnderlineValue = "single"; break;
8108 case LINESTYLE_BOLD: pUnderlineValue = "thick"; break;
8109 case LINESTYLE_DOUBLE: pUnderlineValue = "double"; break;
8110 case LINESTYLE_DOTTED: pUnderlineValue = "dotted"; break;
8111 case LINESTYLE_DASH: pUnderlineValue = "dash"; break;
8112 case LINESTYLE_DASHDOT: pUnderlineValue = "dotDash"; break;
8113 case LINESTYLE_DASHDOTDOT: pUnderlineValue = "dotDotDash"; break;
8114 case LINESTYLE_WAVE: pUnderlineValue = "wave"; break;
8115 case LINESTYLE_BOLDDOTTED: pUnderlineValue = "dottedHeavy"; break;
8116 case LINESTYLE_BOLDDASH: pUnderlineValue = "dashedHeavy"; break;
8117 case LINESTYLE_LONGDASH: pUnderlineValue = "dashLongHeavy"; break;
8118 case LINESTYLE_BOLDLONGDASH: pUnderlineValue = "dashLongHeavy"; break;
8119 case LINESTYLE_BOLDDASHDOT: pUnderlineValue = "dashDotHeavy"; break;
8120 case LINESTYLE_BOLDDASHDOTDOT: pUnderlineValue = "dashDotDotHeavy"; break;
8121 case LINESTYLE_BOLDWAVE: pUnderlineValue = "wavyHeavy"; break;
8122 case LINESTYLE_DOUBLEWAVE: pUnderlineValue = "wavyDouble"; break;
8123 case LINESTYLE_NONE: // fall through
8124 default: pUnderlineValue = "none"; break;
8127 Color aUnderlineColor = rUnderline.GetColor();
8128 bool bUnderlineHasColor = !aUnderlineColor.IsTransparent();
8129 if (bUnderlineHasColor)
8131 model::ComplexColor const& rComplexColor = rUnderline.getComplexColor();
8132 // Underline has a color
8133 rtl::Reference<FastAttributeList> pAttrList = FastSerializerHelper::createAttrList();
8134 pAttrList->add(FSNS(XML_w, XML_val), pUnderlineValue);
8135 pAttrList->add(FSNS(XML_w, XML_color), msfilter::util::ConvertColor(aUnderlineColor));
8136 lclAddThemeColorAttributes(pAttrList, rComplexColor);
8137 m_pSerializer->singleElementNS(XML_w, XML_u, pAttrList);
8140 else
8142 // Underline has no color
8143 m_pSerializer->singleElementNS(XML_w, XML_u, FSNS(XML_w, XML_val), pUnderlineValue);
8147 void DocxAttributeOutput::CharWeight( const SvxWeightItem& rWeight )
8149 if ( rWeight.GetWeight() == WEIGHT_BOLD )
8150 m_pSerializer->singleElementNS(XML_w, XML_b);
8151 else
8152 m_pSerializer->singleElementNS(XML_w, XML_b, FSNS(XML_w, XML_val), "false");
8155 void DocxAttributeOutput::CharAutoKern( const SvxAutoKernItem& rAutoKern )
8157 // auto kerning is bound to a minimum font size in Word - but is just a boolean in Writer :-(
8158 // 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.)
8159 const OString sFontSize = OString::number( static_cast<sal_uInt32>(rAutoKern.GetValue()) * 2 );
8160 m_pSerializer->singleElementNS(XML_w, XML_kern, FSNS(XML_w, XML_val), sFontSize);
8163 void DocxAttributeOutput::CharAnimatedText( const SvxBlinkItem& rBlink )
8165 if ( rBlink.GetValue() )
8166 m_pSerializer->singleElementNS(XML_w, XML_effect, FSNS(XML_w, XML_val), "blinkBackground");
8167 else
8168 m_pSerializer->singleElementNS(XML_w, XML_effect, FSNS(XML_w, XML_val), "none");
8171 constexpr OUStringLiteral MSWORD_CH_SHADING_FILL = u"FFFFFF"; // The attribute w:fill of w:shd, for MS-Word's character shading,
8172 constexpr OUStringLiteral MSWORD_CH_SHADING_COLOR = u"auto"; // The attribute w:color of w:shd, for MS-Word's character shading,
8173 constexpr OUStringLiteral MSWORD_CH_SHADING_VAL = u"pct15"; // The attribute w:value of w:shd, for MS-Word's character shading,
8175 void DocxAttributeOutput::CharBackground( const SvxBrushItem& rBrush )
8177 // Check if the brush shading pattern is 'PCT15'. If so - write it back to the DOCX
8178 if (rBrush.GetShadingValue() == ShadingPattern::PCT15)
8180 m_pSerializer->singleElementNS( XML_w, XML_shd,
8181 FSNS( XML_w, XML_val ), MSWORD_CH_SHADING_VAL,
8182 FSNS( XML_w, XML_color ), MSWORD_CH_SHADING_COLOR,
8183 FSNS( XML_w, XML_fill ), MSWORD_CH_SHADING_FILL );
8185 else
8187 m_pSerializer->singleElementNS( XML_w, XML_shd,
8188 FSNS( XML_w, XML_fill ), msfilter::util::ConvertColor(rBrush.GetColor()),
8189 FSNS( XML_w, XML_val ), "clear" );
8193 void DocxAttributeOutput::CharFontCJK( const SvxFontItem& rFont )
8195 if (m_pFontsAttrList && m_pFontsAttrList->hasAttribute(FSNS(XML_w, XML_eastAsia)))
8197 // tdf#38778: do to fields output into DOC the font could be added before and after field declaration
8198 // that all sub runs of the field will have correct font inside.
8199 // For DOCX we should do not add the same font information twice in the same node
8200 return;
8203 AddToAttrList( m_pFontsAttrList, FSNS( XML_w, XML_eastAsia ), rFont.GetFamilyName() );
8206 void DocxAttributeOutput::CharPostureCJK( const SvxPostureItem& rPosture )
8208 if ( rPosture.GetPosture() != ITALIC_NONE )
8209 m_pSerializer->singleElementNS(XML_w, XML_i);
8210 else
8211 m_pSerializer->singleElementNS(XML_w, XML_i, FSNS(XML_w, XML_val), "false");
8214 void DocxAttributeOutput::CharWeightCJK( const SvxWeightItem& rWeight )
8216 if ( rWeight.GetWeight() == WEIGHT_BOLD )
8217 m_pSerializer->singleElementNS(XML_w, XML_b);
8218 else
8219 m_pSerializer->singleElementNS(XML_w, XML_b, FSNS(XML_w, XML_val), "false");
8222 void DocxAttributeOutput::CharFontCTL( const SvxFontItem& rFont )
8224 if (m_pFontsAttrList && m_pFontsAttrList->hasAttribute(FSNS(XML_w, XML_cs)))
8226 // tdf#38778: do to fields output into DOC the font could be added before and after field declaration
8227 // that all sub runs of the field will have correct font inside.
8228 // For DOCX we should do not add the same font information twice in the same node
8229 return;
8232 AddToAttrList( m_pFontsAttrList, FSNS( XML_w, XML_cs ), rFont.GetFamilyName() );
8235 void DocxAttributeOutput::CharPostureCTL( const SvxPostureItem& rPosture)
8237 if ( rPosture.GetPosture() != ITALIC_NONE )
8238 m_pSerializer->singleElementNS(XML_w, XML_iCs);
8239 else
8240 m_pSerializer->singleElementNS(XML_w, XML_iCs, FSNS(XML_w, XML_val), "false");
8243 void DocxAttributeOutput::CharWeightCTL( const SvxWeightItem& rWeight )
8245 if ( rWeight.GetWeight() == WEIGHT_BOLD )
8246 m_pSerializer->singleElementNS(XML_w, XML_bCs);
8247 else
8248 m_pSerializer->singleElementNS(XML_w, XML_bCs, FSNS(XML_w, XML_val), "false");
8251 void DocxAttributeOutput::CharBidiRTL( const SfxPoolItem& )
8255 void DocxAttributeOutput::CharIdctHint( const SfxPoolItem& )
8259 void DocxAttributeOutput::CharRotate( const SvxCharRotateItem& rRotate)
8261 // Not rotated?
8262 if ( !rRotate.GetValue())
8263 return;
8265 AddToAttrList( m_pEastAsianLayoutAttrList, FSNS( XML_w, XML_vert ), "true" );
8267 if (rRotate.IsFitToLine())
8268 AddToAttrList( m_pEastAsianLayoutAttrList, FSNS( XML_w, XML_vertCompress ), "true" );
8271 void DocxAttributeOutput::CharEmphasisMark( const SvxEmphasisMarkItem& rEmphasisMark )
8273 const char *pEmphasis;
8274 const FontEmphasisMark v = rEmphasisMark.GetEmphasisMark();
8276 if (v == (FontEmphasisMark::Dot | FontEmphasisMark::PosAbove))
8277 pEmphasis = "dot";
8278 else if (v == (FontEmphasisMark::Accent | FontEmphasisMark::PosAbove))
8279 pEmphasis = "comma";
8280 else if (v == (FontEmphasisMark::Circle | FontEmphasisMark::PosAbove))
8281 pEmphasis = "circle";
8282 else if (v == (FontEmphasisMark::Dot|FontEmphasisMark::PosBelow))
8283 pEmphasis = "underDot";
8284 else
8285 pEmphasis = "none";
8287 m_pSerializer->singleElementNS(XML_w, XML_em, FSNS(XML_w, XML_val), pEmphasis);
8290 void DocxAttributeOutput::CharTwoLines( const SvxTwoLinesItem& rTwoLines )
8292 if ( !rTwoLines.GetValue() )
8293 return;
8295 AddToAttrList( m_pEastAsianLayoutAttrList, FSNS( XML_w, XML_combine ), "true" );
8297 sal_Unicode cStart = rTwoLines.GetStartBracket();
8298 sal_Unicode cEnd = rTwoLines.GetEndBracket();
8300 if (!cStart && !cEnd)
8301 return;
8303 std::string_view sBracket;
8304 if ((cStart == '{') || (cEnd == '}'))
8305 sBracket = "curly";
8306 else if ((cStart == '<') || (cEnd == '>'))
8307 sBracket = "angle";
8308 else if ((cStart == '[') || (cEnd == ']'))
8309 sBracket = "square";
8310 else
8311 sBracket = "round";
8312 AddToAttrList( m_pEastAsianLayoutAttrList, FSNS( XML_w, XML_combineBrackets ), sBracket );
8315 void DocxAttributeOutput::CharScaleWidth( const SvxCharScaleWidthItem& rScaleWidth )
8317 // Clamp CharScaleWidth to OOXML limits ([1..600])
8318 const sal_Int16 nScaleWidth( std::max<sal_Int16>( 1,
8319 std::min<sal_Int16>( rScaleWidth.GetValue(), 600 ) ) );
8320 m_pSerializer->singleElementNS( XML_w, XML_w,
8321 FSNS( XML_w, XML_val ), OString::number(nScaleWidth) );
8324 void DocxAttributeOutput::CharRelief( const SvxCharReliefItem& rRelief )
8326 switch ( rRelief.GetValue() )
8328 case FontRelief::Embossed:
8329 m_pSerializer->singleElementNS(XML_w, XML_emboss);
8330 break;
8331 case FontRelief::Engraved:
8332 m_pSerializer->singleElementNS(XML_w, XML_imprint);
8333 break;
8334 default:
8335 m_pSerializer->singleElementNS(XML_w, XML_emboss, FSNS(XML_w, XML_val), "false");
8336 m_pSerializer->singleElementNS(XML_w, XML_imprint, FSNS(XML_w, XML_val), "false");
8337 break;
8341 void DocxAttributeOutput::CharHidden( const SvxCharHiddenItem& rHidden )
8343 if ( rHidden.GetValue() )
8345 m_pSerializer->singleElementNS(XML_w, XML_vanish);
8346 // export specVanish for inline headings
8347 if (m_bOpenedParaPr && m_rExport.m_bParaInlineHeading)
8349 m_pSerializer->singleElementNS(XML_w, XML_specVanish);
8350 // don't export extra vanish/specVanish
8351 m_rExport.m_bParaInlineHeading = false;
8354 else
8355 m_pSerializer->singleElementNS(XML_w, XML_vanish, FSNS(XML_w, XML_val), "false");
8358 void DocxAttributeOutput::CharBorder(
8359 const SvxBorderLine* pAllBorder, const sal_uInt16 nDist, const bool bShadow )
8361 css::table::BorderLine2 rStyleBorder;
8362 const SvxBoxItem* pInherited = nullptr;
8363 if ( GetExport().m_bStyDef && GetExport().m_pCurrentStyle && GetExport().m_pCurrentStyle->DerivedFrom() )
8364 pInherited = GetExport().m_pCurrentStyle->DerivedFrom()->GetAttrSet().GetItem<SvxBoxItem>(RES_CHRATR_BOX);
8365 else if ( m_rExport.m_pChpIter ) // incredibly undocumented, but this is the character-style info, right?
8367 if (const SvxBoxItem* pPoolItem = GetExport().m_pChpIter->HasTextItem(RES_CHRATR_BOX))
8369 pInherited = pPoolItem;
8373 if ( pInherited )
8374 rStyleBorder = SvxBoxItem::SvxLineToLine(pInherited->GetRight(), false);
8376 impl_borderLine( m_pSerializer, XML_bdr, pAllBorder, nDist, bShadow, &rStyleBorder );
8379 void DocxAttributeOutput::CharHighlight( const SvxBrushItem& rHighlight )
8381 const OString sColor = TransHighlightColor( msfilter::util::TransColToIco(rHighlight.GetColor()) );
8382 if ( !sColor.isEmpty() )
8384 m_pSerializer->singleElementNS(XML_w, XML_highlight, FSNS(XML_w, XML_val), sColor);
8388 void DocxAttributeOutput::TextINetFormat( const SwFormatINetFormat& rLink )
8390 const SwCharFormat* pFormat = m_rExport.m_rDoc.FindCharFormatByName(rLink.GetINetFormat());
8391 if (pFormat)
8393 OString aStyleId(m_rExport.m_pStyles->GetStyleId(m_rExport.GetId(pFormat)));
8394 if (!aStyleId.equalsIgnoreAsciiCase("DefaultStyle"))
8395 m_pSerializer->singleElementNS(XML_w, XML_rStyle, FSNS(XML_w, XML_val), aStyleId);
8399 void DocxAttributeOutput::TextCharFormat( const SwFormatCharFormat& rCharFormat )
8401 OString aStyleId(m_rExport.m_pStyles->GetStyleId(m_rExport.GetId(rCharFormat.GetCharFormat())));
8403 m_pSerializer->singleElementNS(XML_w, XML_rStyle, FSNS(XML_w, XML_val), aStyleId);
8406 void DocxAttributeOutput::RefField( const SwField& rField, const OUString& rRef )
8408 SwFieldIds nType = rField.GetTyp( )->Which( );
8409 if ( nType == SwFieldIds::GetExp )
8411 OUString sCmd = FieldString( ww::eREF ) +
8412 "\"" + rRef + "\" ";
8414 m_rExport.OutputField( &rField, ww::eREF, sCmd );
8417 // There is nothing to do here for the set fields
8420 void DocxAttributeOutput::HiddenField(const SwField& /*rField*/)
8422 SAL_INFO("sw.ww8", "TODO DocxAttributeOutput::HiddenField()" );
8425 void DocxAttributeOutput::PostitField( const SwField* pField )
8427 assert( dynamic_cast< const SwPostItField* >( pField ));
8428 const SwPostItField* pPostItField = static_cast<const SwPostItField*>(pField);
8429 sal_Int32 nId = 0;
8430 auto it = m_rOpenedAnnotationMarksIds.find(pPostItField->GetName());
8431 if (it != m_rOpenedAnnotationMarksIds.end())
8432 // If the postit field has an annotation mark associated, we already have an id.
8433 nId = it->second;
8434 else
8435 // Otherwise get a new one.
8436 nId = m_nNextAnnotationMarkId++;
8437 m_postitFields.emplace_back(pPostItField, PostItDOCXData{ nId });
8440 void DocxAttributeOutput::WritePostitFieldReference()
8442 while( m_postitFieldsMaxId < m_postitFields.size())
8444 OString idstr = OString::number(m_postitFields[m_postitFieldsMaxId].second.id);
8446 // In case this file is inside annotation marks, we want to write the
8447 // comment reference after the annotation mark is closed, not here.
8448 const OUString& idname = m_postitFields[m_postitFieldsMaxId].first->GetName();
8449 auto it = m_rOpenedAnnotationMarksIds.find( idname );
8450 if ( it == m_rOpenedAnnotationMarksIds.end( ) )
8451 m_pSerializer->singleElementNS(XML_w, XML_commentReference, FSNS(XML_w, XML_id), idstr);
8452 ++m_postitFieldsMaxId;
8456 DocxAttributeOutput::hasProperties DocxAttributeOutput::WritePostitFields()
8458 bool bRemovePersonalInfo = SvtSecurityOptions::IsOptionSet(
8459 SvtSecurityOptions::EOption::DocWarnRemovePersonalInfo ) && !SvtSecurityOptions::IsOptionSet(
8460 SvtSecurityOptions::EOption::DocWarnKeepRedlineInfo);
8462 hasProperties eResult = hasProperties::no;
8463 for (auto& [f1, data1] : m_postitFields)
8465 if (f1->GetParentId() != 0 || f1->GetParentPostItId() != 0)
8467 for (size_t i = 0; i < m_postitFields.size(); i++)
8469 auto& [f2, data2] = m_postitFields[i];
8470 if ((f1->GetParentId() != 0 && f2->GetParaId() == f1->GetParentId())
8471 || (f1->GetParentPostItId() != 0
8472 && f2->GetPostItId() == f1->GetParentPostItId()))
8474 if (data2.parentStatus == ParentStatus::None)
8475 data2.parentStatus = ParentStatus::IsParent;
8476 data1.parentStatus = ParentStatus::HasParent;
8477 data1.parentIndex = i;
8478 break;
8483 for (auto& [f, data] : m_postitFields)
8485 const DateTime aDateTime = f->GetDateTime();
8486 bool bNoDate = bRemovePersonalInfo ||
8487 ( aDateTime.GetYear() == 1970 && aDateTime.GetMonth() == 1 && aDateTime.GetDay() == 1 );
8489 rtl::Reference<sax_fastparser::FastAttributeList> pAttributeList
8490 = sax_fastparser::FastSerializerHelper::createAttrList();
8492 pAttributeList->add(FSNS( XML_w, XML_id ), OString::number(data.id));
8493 pAttributeList->add(FSNS( XML_w, XML_author ), bRemovePersonalInfo
8494 ? "Author" + OString::number( GetExport().GetInfoID(f->GetPar1()) )
8495 : f->GetPar1().toUtf8());
8496 if (!bNoDate)
8497 pAttributeList->add(FSNS( XML_w, XML_date ), DateTimeToOString( aDateTime ));
8498 pAttributeList->add(FSNS( XML_w, XML_initials ), bRemovePersonalInfo
8499 ? OString::number( GetExport().GetInfoID(f->GetInitials()) )
8500 : f->GetInitials().toUtf8());
8501 m_pSerializer->startElementNS( XML_w, XML_comment, pAttributeList );
8503 // Make sure to give parent/child fields a paraId
8504 const bool bNeedParaId = f->GetResolved() || data.parentStatus != ParentStatus::None;
8505 if (bNeedParaId)
8506 eResult = hasProperties::yes;
8508 if (f->GetTextObject() != nullptr)
8510 // richtext
8511 data.lastParaId = GetExport().WriteOutliner(*f->GetTextObject(), TXT_ATN, bNeedParaId);
8513 else
8515 // just plain text - eg. when the field was created via the
8516 // .uno:InsertAnnotation API
8517 std::optional<OUString> aParaId;
8518 if (bNeedParaId)
8520 data.lastParaId = m_nNextParaId++;
8521 aParaId = NumberToHexBinary(data.lastParaId);
8523 m_pSerializer->startElementNS(XML_w, XML_p, FSNS(XML_w14, XML_paraId), aParaId);
8524 m_pSerializer->startElementNS(XML_w, XML_r);
8525 RunText(f->GetText());
8526 m_pSerializer->endElementNS(XML_w, XML_r);
8527 m_pSerializer->endElementNS(XML_w, XML_p);
8530 m_pSerializer->endElementNS( XML_w, XML_comment );
8532 return eResult;
8535 void DocxAttributeOutput::WritePostItFieldsResolved()
8537 for (auto& [f, data] : m_postitFields)
8539 // Parent fields don't need to be exported here if they don't have a resolved attribute
8540 if (!f->GetResolved() && data.parentStatus != ParentStatus::HasParent)
8541 continue;
8542 OUString idstr = NumberToHexBinary(data.lastParaId);
8543 std::optional<OUString> sDone, sParentId;
8544 if (f->GetParentId() != 0 || f->GetParentPostItId() != 0)
8546 if (data.parentStatus == ParentStatus::HasParent)
8548 // Since parent fields have been resolved first, they should already have an id
8549 const PostItDOCXData& aParentFieldData = m_postitFields[data.parentIndex].second;
8550 sParentId = NumberToHexBinary(aParentFieldData.lastParaId);
8552 else
8554 SAL_WARN("sw.ww8", "SwPostItField has a parent id, but a matching parent was not found");
8557 if (f->GetResolved())
8558 sDone = "1";
8559 m_pSerializer->singleElementNS(XML_w15, XML_commentEx,
8560 FSNS(XML_w15, XML_paraId), idstr,
8561 FSNS(XML_w15, XML_done), sDone,
8562 FSNS(XML_w15, XML_paraIdParent), sParentId);
8566 bool DocxAttributeOutput::DropdownField( const SwField* pField )
8568 ww::eField eType = ww::eFORMDROPDOWN;
8569 OUString sCmd = FieldString( eType );
8570 GetExport( ).OutputField( pField, eType, sCmd );
8572 return false;
8575 bool DocxAttributeOutput::PlaceholderField( const SwField* pField )
8577 assert( m_PendingPlaceholder == nullptr );
8578 m_PendingPlaceholder = pField;
8579 return false; // do not expand
8582 void DocxAttributeOutput::WritePendingPlaceholder()
8584 if( m_PendingPlaceholder == nullptr )
8585 return;
8586 const SwField* pField = m_PendingPlaceholder;
8587 m_PendingPlaceholder = nullptr;
8588 m_pSerializer->startElementNS(XML_w, XML_sdt);
8589 m_pSerializer->startElementNS(XML_w, XML_sdtPr);
8590 if( !pField->GetPar2().isEmpty())
8591 m_pSerializer->singleElementNS(XML_w, XML_alias, FSNS(XML_w, XML_val), pField->GetPar2());
8592 m_pSerializer->singleElementNS(XML_w, XML_temporary);
8593 m_pSerializer->singleElementNS(XML_w, XML_showingPlcHdr);
8594 m_pSerializer->singleElementNS(XML_w, XML_text);
8595 m_pSerializer->endElementNS( XML_w, XML_sdtPr );
8596 m_pSerializer->startElementNS(XML_w, XML_sdtContent);
8597 m_pSerializer->startElementNS(XML_w, XML_r);
8598 RunText( pField->GetPar1());
8599 m_pSerializer->endElementNS( XML_w, XML_r );
8600 m_pSerializer->endElementNS( XML_w, XML_sdtContent );
8601 m_pSerializer->endElementNS( XML_w, XML_sdt );
8604 void DocxAttributeOutput::SetField( const SwField& rField, ww::eField eType, const OUString& rCmd )
8606 // field bookmarks are handled in the EndRun method
8607 GetExport().OutputField(&rField, eType, rCmd );
8610 void DocxAttributeOutput::WriteExpand( const SwField* pField )
8612 // Will be written in the next End Run
8613 m_rExport.OutputField( pField, ww::eUNKNOWN, OUString() );
8616 void DocxAttributeOutput::WriteField_Impl(const SwField *const pField,
8617 ww::eField const eType, const OUString& rFieldCmd, FieldFlags const nMode,
8618 OUString const*const pBookmarkName)
8620 if (m_bPreventDoubleFieldsHandling)
8621 return;
8623 struct FieldInfos infos;
8624 if (pField)
8625 infos.pField = pField->CopyField();
8626 infos.sCmd = rFieldCmd;
8627 infos.eType = eType;
8628 infos.bClose = bool(FieldFlags::Close & nMode);
8629 infos.bSep = bool(FieldFlags::CmdEnd & nMode);
8630 infos.bOpen = bool(FieldFlags::Start & nMode);
8631 m_Fields.push_back( infos );
8633 if (pBookmarkName)
8635 m_sFieldBkm = *pBookmarkName;
8638 if ( !pField )
8639 return;
8641 SwFieldIds nType = pField->GetTyp( )->Which( );
8642 sal_uInt16 nSubType = pField->GetSubType();
8644 // TODO Any other field types here ?
8645 if ( ( nType == SwFieldIds::SetExp ) && ( nSubType & nsSwGetSetExpType::GSE_STRING ) )
8647 const SwSetExpField *pSet = static_cast<const SwSetExpField*>( pField );
8648 m_sFieldBkm = pSet->GetPar1( );
8650 else if ( nType == SwFieldIds::Dropdown )
8652 const SwDropDownField* pDropDown = static_cast<const SwDropDownField*>( pField );
8653 m_sFieldBkm = pDropDown->GetName( );
8657 void DocxAttributeOutput::WriteFormData_Impl( const ::sw::mark::Fieldmark& rFieldmark )
8659 if ( !m_Fields.empty() )
8660 m_Fields.begin()->pFieldmark = &rFieldmark;
8663 void DocxAttributeOutput::WriteBookmarks_Impl( std::vector< OUString >& rStarts, std::vector< OUString >& rEnds, const SwRedlineData* pRedlineData )
8665 for ( const OUString & name : rStarts )
8667 if (name.startsWith("permission-for-group:") ||
8668 name.startsWith("permission-for-user:"))
8670 m_rPermissionsStart.push_back(name);
8672 else
8674 m_rBookmarksStart.push_back(name);
8675 m_pMoveRedlineData = const_cast<SwRedlineData*>(pRedlineData);
8678 rStarts.clear();
8680 for ( const OUString & name : rEnds )
8682 if (name.startsWith("permission-for-group:") ||
8683 name.startsWith("permission-for-user:"))
8685 m_rPermissionsEnd.push_back(name);
8687 else
8689 m_rBookmarksEnd.push_back(name);
8692 rEnds.clear();
8695 void DocxAttributeOutput::WriteFinalBookmarks_Impl( std::vector< OUString >& rStarts, std::vector< OUString >& rEnds )
8697 for ( const OUString & name : rStarts )
8699 if (name.startsWith("permission-for-group:") ||
8700 name.startsWith("permission-for-user:"))
8702 m_rPermissionsStart.push_back(name);
8704 else
8706 m_rFinalBookmarksStart.push_back(name);
8709 rStarts.clear();
8711 for ( const OUString & name : rEnds )
8713 if (name.startsWith("permission-for-group:") ||
8714 name.startsWith("permission-for-user:"))
8716 m_rPermissionsEnd.push_back(name);
8718 else
8720 m_rFinalBookmarksEnd.push_back(name);
8723 rEnds.clear();
8726 void DocxAttributeOutput::WriteAnnotationMarks_Impl( std::vector< OUString >& rStarts,
8727 std::vector< OUString >& rEnds )
8729 m_rAnnotationMarksStart.insert(m_rAnnotationMarksStart.end(), rStarts.begin(), rStarts.end());
8730 rStarts.clear();
8732 m_rAnnotationMarksEnd.insert(m_rAnnotationMarksEnd.end(), rEnds.begin(), rEnds.end());
8733 rEnds.clear();
8736 void DocxAttributeOutput::TextFootnote_Impl( const SwFormatFootnote& rFootnote )
8738 const SwEndNoteInfo& rInfo = rFootnote.IsEndNote()?
8739 m_rExport.m_rDoc.GetEndNoteInfo(): m_rExport.m_rDoc.GetFootnoteInfo();
8741 // footnote/endnote run properties
8742 const SwCharFormat* pCharFormat = rInfo.GetAnchorCharFormat( m_rExport.m_rDoc );
8744 OString aStyleId(m_rExport.m_pStyles->GetStyleId(m_rExport.GetId(pCharFormat)));
8746 m_pSerializer->singleElementNS(XML_w, XML_rStyle, FSNS(XML_w, XML_val), aStyleId);
8748 // remember the footnote/endnote to
8749 // 1) write the footnoteReference/endnoteReference in EndRunProperties()
8750 // 2) be able to dump them all to footnotes.xml/endnotes.xml
8751 if ( !rFootnote.IsEndNote() && m_rExport.m_rDoc.GetFootnoteInfo().m_ePos != FTNPOS_CHAPTER )
8752 m_pFootnotesList->add( rFootnote );
8753 else
8754 m_pEndnotesList->add( rFootnote );
8757 void DocxAttributeOutput::FootnoteEndnoteReference()
8759 sal_Int32 nId;
8760 const SwFormatFootnote *pFootnote = m_pFootnotesList->getCurrent( nId );
8761 sal_Int32 nToken = XML_footnoteReference;
8763 // both cannot be set at the same time - if they are, it's a bug
8764 if ( !pFootnote )
8766 pFootnote = m_pEndnotesList->getCurrent( nId );
8767 nToken = XML_endnoteReference;
8770 if ( !pFootnote )
8771 return;
8773 // write it
8774 if ( pFootnote->GetNumStr().isEmpty() )
8776 // autonumbered
8777 m_pSerializer->singleElementNS(XML_w, nToken, FSNS(XML_w, XML_id), OString::number(nId));
8779 else
8781 // not autonumbered
8782 m_pSerializer->singleElementNS( XML_w, nToken,
8783 FSNS( XML_w, XML_customMarkFollows ), "1",
8784 FSNS( XML_w, XML_id ), OString::number(nId) );
8786 RunText( pFootnote->GetNumStr() );
8790 static void WriteFootnoteSeparatorHeight(
8791 ::sax_fastparser::FSHelperPtr const& pSerializer, SwTwips const nHeight)
8793 // try to get the height by setting font size of the paragraph
8794 if (nHeight != 0)
8796 pSerializer->startElementNS(XML_w, XML_pPr);
8797 pSerializer->startElementNS(XML_w, XML_rPr);
8798 pSerializer->singleElementNS(XML_w, XML_sz, FSNS(XML_w, XML_val),
8799 OString::number((nHeight + 5) / 10));
8800 pSerializer->endElementNS(XML_w, XML_rPr);
8801 pSerializer->endElementNS(XML_w, XML_pPr);
8805 void DocxAttributeOutput::FootnotesEndnotes( bool bFootnotes )
8807 const FootnotesVector& rVector = bFootnotes? m_pFootnotesList->getVector(): m_pEndnotesList->getVector();
8809 sal_Int32 nBody = bFootnotes? XML_footnotes: XML_endnotes;
8810 sal_Int32 nItem = bFootnotes? XML_footnote: XML_endnote;
8812 m_pSerializer->startElementNS( XML_w, nBody, m_rExport.MainXmlNamespaces() );
8814 sal_Int32 nIndex = 0;
8816 // separator
8817 // note: can only be defined for the whole document, not per section
8818 m_pSerializer->startElementNS( XML_w, nItem,
8819 FSNS( XML_w, XML_id ), OString::number(nIndex++),
8820 FSNS( XML_w, XML_type ), "separator" );
8821 m_pSerializer->startElementNS(XML_w, XML_p);
8823 bool bSeparator = true;
8824 SwTwips nHeight(0);
8825 if (bFootnotes)
8827 const SwPageFootnoteInfo& rFootnoteInfo = m_rExport.m_rDoc.GetPageDesc(0).GetFootnoteInfo();
8828 // Request separator only if both width and thickness are non-zero.
8829 bSeparator = rFootnoteInfo.GetLineStyle() != SvxBorderLineStyle::NONE
8830 && rFootnoteInfo.GetLineWidth() > 0
8831 && double(rFootnoteInfo.GetWidth()) > 0;
8832 nHeight = sw::FootnoteSeparatorHeight(m_rExport.m_rDoc, rFootnoteInfo);
8834 const IDocumentSettingAccess& rIDSA = m_rExport.m_rDoc.getIDocumentSettingAccess();
8835 if (rIDSA.get(DocumentSettingId::CONTINUOUS_ENDNOTES))
8837 // Don't request separator if this is a Word-style separator, which is handled at a
8838 // layout level.
8839 nHeight = 0;
8843 WriteFootnoteSeparatorHeight(m_pSerializer, nHeight);
8845 m_pSerializer->startElementNS(XML_w, XML_r);
8846 if (bSeparator)
8847 m_pSerializer->singleElementNS(XML_w, XML_separator);
8848 m_pSerializer->endElementNS( XML_w, XML_r );
8849 m_pSerializer->endElementNS( XML_w, XML_p );
8850 m_pSerializer->endElementNS( XML_w, nItem );
8852 // separator
8853 m_pSerializer->startElementNS( XML_w, nItem,
8854 FSNS( XML_w, XML_id ), OString::number(nIndex++),
8855 FSNS( XML_w, XML_type ), "continuationSeparator" );
8856 m_pSerializer->startElementNS(XML_w, XML_p);
8858 WriteFootnoteSeparatorHeight(m_pSerializer, nHeight);
8860 m_pSerializer->startElementNS(XML_w, XML_r);
8861 if (bSeparator)
8863 m_pSerializer->singleElementNS(XML_w, XML_continuationSeparator);
8865 m_pSerializer->endElementNS( XML_w, XML_r );
8866 m_pSerializer->endElementNS( XML_w, XML_p );
8867 m_pSerializer->endElementNS( XML_w, nItem );
8869 // if new special ones are added, update also WriteFootnoteEndnotePr()
8871 // footnotes/endnotes themselves
8872 for ( const auto& rpItem : rVector )
8874 m_footnoteEndnoteRefTag = bFootnotes ? XML_footnoteRef : XML_endnoteRef;
8875 m_footnoteCustomLabel = rpItem->GetNumStr();
8877 m_pSerializer->startElementNS(XML_w, nItem, FSNS(XML_w, XML_id), OString::number(nIndex));
8879 const SwNodeIndex* pIndex = rpItem->GetTextFootnote()->GetStartNode();
8880 m_rExport.WriteSpecialText( pIndex->GetIndex() + 1,
8881 pIndex->GetNode().EndOfSectionIndex(),
8882 bFootnotes? TXT_FTN: TXT_EDN );
8884 m_pSerializer->endElementNS( XML_w, nItem );
8885 ++nIndex;
8888 m_pSerializer->endElementNS( XML_w, nBody );
8892 void DocxAttributeOutput::WriteFootnoteEndnotePr( ::sax_fastparser::FSHelperPtr const & fs, int tag,
8893 const SwEndNoteInfo& info, int listtag )
8895 fs->startElementNS(XML_w, tag);
8897 SwSectionFormats& rSections = m_rExport.m_rDoc.GetSections();
8898 if (!rSections.empty())
8900 SwSectionFormat* pFormat = rSections[0];
8901 bool bEndnAtEnd = pFormat->GetEndAtTextEnd().IsAtEnd();
8902 if (bEndnAtEnd)
8904 fs->singleElementNS(XML_w, XML_pos, FSNS(XML_w, XML_val), "sectEnd");
8908 OString aCustomFormat;
8909 OString fmt = lcl_ConvertNumberingType(info.m_aFormat.GetNumberingType(), nullptr, aCustomFormat);
8910 if (!fmt.isEmpty() && aCustomFormat.isEmpty())
8911 fs->singleElementNS(XML_w, XML_numFmt, FSNS(XML_w, XML_val), fmt);
8912 if( info.m_nFootnoteOffset != 0 )
8913 fs->singleElementNS( XML_w, XML_numStart, FSNS( XML_w, XML_val ),
8914 OString::number(info.m_nFootnoteOffset + 1) );
8916 const SwFootnoteInfo* pFootnoteInfo = dynamic_cast<const SwFootnoteInfo*>(&info);
8917 if( pFootnoteInfo )
8919 switch( pFootnoteInfo->m_eNum )
8921 case FTNNUM_PAGE: fmt = "eachPage"_ostr; break;
8922 case FTNNUM_CHAPTER: fmt = "eachSect"_ostr; break;
8923 default: fmt.clear(); break;
8925 if (!fmt.isEmpty())
8926 fs->singleElementNS(XML_w, XML_numRestart, FSNS(XML_w, XML_val), fmt);
8929 if( listtag != 0 ) // we are writing to settings.xml, write also special footnote/endnote list
8930 { // there are currently only two hardcoded ones ( see FootnotesEndnotes())
8931 fs->singleElementNS(XML_w, listtag, FSNS(XML_w, XML_id), "0");
8932 fs->singleElementNS(XML_w, listtag, FSNS(XML_w, XML_id), "1");
8934 fs->endElementNS( XML_w, tag );
8937 void DocxAttributeOutput::SectFootnoteEndnotePr()
8939 if( HasFootnotes())
8940 WriteFootnoteEndnotePr( m_pSerializer, XML_footnotePr, m_rExport.m_rDoc.GetFootnoteInfo(), 0 );
8941 if( HasEndnotes())
8942 WriteFootnoteEndnotePr( m_pSerializer, XML_endnotePr, m_rExport.m_rDoc.GetEndNoteInfo(), 0 );
8945 void DocxAttributeOutput::ParaLineSpacing_Impl( short nSpace, short nMulti )
8947 if ( nSpace < 0 )
8949 AddToAttrList( m_pParagraphSpacingAttrList,
8950 FSNS( XML_w, XML_lineRule ), "exact",
8951 FSNS( XML_w, XML_line ), OString::number( -nSpace ) );
8953 else if( nSpace > 0 && nMulti )
8955 AddToAttrList( m_pParagraphSpacingAttrList,
8956 FSNS( XML_w, XML_lineRule ), "auto",
8957 FSNS( XML_w, XML_line ), OString::number( nSpace ) );
8959 else
8961 AddToAttrList( m_pParagraphSpacingAttrList,
8962 FSNS( XML_w, XML_lineRule ), "atLeast",
8963 FSNS( XML_w, XML_line ), OString::number( nSpace ) );
8967 void DocxAttributeOutput::ParaAdjust( const SvxAdjustItem& rAdjust )
8969 const char *pAdjustString;
8971 bool const bEcma = GetExport().GetFilter().getVersion() == oox::core::ECMA_376_1ST_EDITION;
8973 const SfxItemSet* pItems = GetExport().GetCurItemSet();
8974 const SvxFrameDirectionItem* rFrameDir = pItems?
8975 pItems->GetItem( RES_FRAMEDIR ) : nullptr;
8977 SvxFrameDirection nDir = SvxFrameDirection::Environment;
8978 if( rFrameDir != nullptr )
8979 nDir = rFrameDir->GetValue();
8980 if ( nDir == SvxFrameDirection::Environment )
8981 nDir = GetExport( ).GetDefaultFrameDirection( );
8982 bool bRtl = ( nDir == SvxFrameDirection::Horizontal_RL_TB );
8984 switch ( rAdjust.GetAdjust() )
8986 case SvxAdjust::Left:
8987 if ( bEcma )
8989 if ( bRtl )
8990 pAdjustString = "right";
8991 else
8992 pAdjustString = "left";
8994 else if ( bRtl )
8995 pAdjustString = "end";
8996 else
8997 pAdjustString = "start";
8998 break;
8999 case SvxAdjust::Right:
9000 if ( bEcma )
9002 if ( bRtl )
9003 pAdjustString = "left";
9004 else
9005 pAdjustString = "right";
9007 else if ( bRtl )
9008 pAdjustString = "start";
9009 else
9010 pAdjustString = "end";
9011 break;
9012 case SvxAdjust::BlockLine:
9013 case SvxAdjust::Block:
9014 if (rAdjust.GetLastBlock() == SvxAdjust::Block)
9015 pAdjustString = "distribute";
9016 else
9017 pAdjustString = "both";
9018 break;
9019 case SvxAdjust::Center:
9020 pAdjustString = "center";
9021 break;
9022 default:
9023 return; // not supported attribute
9025 m_pSerializer->singleElementNS(XML_w, XML_jc, FSNS(XML_w, XML_val), pAdjustString);
9028 void DocxAttributeOutput::ParaSplit( const SvxFormatSplitItem& rSplit )
9030 if (rSplit.GetValue())
9031 m_pSerializer->singleElementNS(XML_w, XML_keepLines, FSNS(XML_w, XML_val), "false");
9032 else
9033 m_pSerializer->singleElementNS(XML_w, XML_keepLines);
9036 void DocxAttributeOutput::ParaWidows( const SvxWidowsItem& rWidows )
9038 if (rWidows.GetValue())
9039 m_pSerializer->singleElementNS(XML_w, XML_widowControl);
9040 else
9041 m_pSerializer->singleElementNS(XML_w, XML_widowControl, FSNS(XML_w, XML_val), "false");
9044 static void impl_WriteTabElement( FSHelperPtr const & pSerializer,
9045 const SvxTabStop& rTab, tools::Long tabsOffset )
9047 rtl::Reference<FastAttributeList> pTabElementAttrList = FastSerializerHelper::createAttrList();
9049 switch (rTab.GetAdjustment())
9051 case SvxTabAdjust::Right:
9052 pTabElementAttrList->add( FSNS( XML_w, XML_val ), "right" );
9053 break;
9054 case SvxTabAdjust::Decimal:
9055 pTabElementAttrList->add( FSNS( XML_w, XML_val ), "decimal" );
9056 break;
9057 case SvxTabAdjust::Center:
9058 pTabElementAttrList->add( FSNS( XML_w, XML_val ), "center" );
9059 break;
9060 case SvxTabAdjust::Default:
9061 case SvxTabAdjust::Left:
9062 default:
9063 pTabElementAttrList->add( FSNS( XML_w, XML_val ), "left" );
9064 break;
9067 // Write position according to used offset of the whole paragraph.
9068 // In DOCX, w:pos specifies the position of the current custom tab stop with respect to the current page margins.
9069 // But in ODT, zero position could be page margins or paragraph indent according to used settings.
9070 // This is handled outside of this method and provided for us in tabsOffset parameter.
9071 pTabElementAttrList->add( FSNS( XML_w, XML_pos ), OString::number( rTab.GetTabPos() + tabsOffset ) );
9073 sal_Unicode cFillChar = rTab.GetFill();
9075 if ('.' == cFillChar )
9076 pTabElementAttrList->add( FSNS( XML_w, XML_leader ), "dot" );
9077 else if ( '-' == cFillChar )
9078 pTabElementAttrList->add( FSNS( XML_w, XML_leader ), "hyphen" );
9079 else if ( u'\x00B7' == cFillChar ) // middle dot
9080 pTabElementAttrList->add( FSNS( XML_w, XML_leader ), "middleDot" );
9081 else if ( '_' == cFillChar )
9082 pTabElementAttrList->add( FSNS( XML_w, XML_leader ), "underscore" );
9083 else
9084 pTabElementAttrList->add( FSNS( XML_w, XML_leader ), "none" );
9086 pSerializer->singleElementNS(XML_w, XML_tab, pTabElementAttrList);
9089 void DocxAttributeOutput::ParaTabStop( const SvxTabStopItem& rTabStop )
9091 const SvxTabStopItem* pInheritedTabs = nullptr;
9092 if ( GetExport().m_pStyAttr )
9093 pInheritedTabs = GetExport().m_pStyAttr->GetItem<SvxTabStopItem>(RES_PARATR_TABSTOP);
9094 else if ( GetExport().m_pCurrentStyle && GetExport().m_pCurrentStyle->DerivedFrom() )
9095 pInheritedTabs = GetExport().m_pCurrentStyle->DerivedFrom()->GetAttrSet().GetItem<SvxTabStopItem>(RES_PARATR_TABSTOP);
9096 const sal_uInt16 nInheritedTabCount = pInheritedTabs ? pInheritedTabs->Count() : 0;
9097 const sal_uInt16 nCount = rTabStop.Count();
9099 // <w:tabs> must contain at least one <w:tab>, so don't write it empty
9100 if ( !nCount && !nInheritedTabCount )
9101 return;
9102 if( nCount == 1 && rTabStop[ 0 ].GetAdjustment() == SvxTabAdjust::Default )
9104 GetExport().setDefaultTabStop( rTabStop[ 0 ].GetTabPos());
9105 return;
9108 // do not output inherited tabs twice (inside styles and inside inline properties)
9109 if ( nCount == nInheritedTabCount && nCount > 0 )
9111 if ( *pInheritedTabs == rTabStop )
9112 return;
9115 m_pSerializer->startElementNS(XML_w, XML_tabs);
9117 // Get offset for tabs
9118 // In DOCX, w:pos specifies the position of the current custom tab stop with respect to the current page margins.
9119 // But in ODT, zero position could be page margins or paragraph indent according to used settings.
9120 tools::Long tabsOffset = m_rExport.GetParaTabStopOffset();
9122 // clear unused inherited tabs - otherwise the style will add them back in
9123 sal_Int32 nCurrTab = 0;
9124 for ( sal_uInt16 i = 0; i < nInheritedTabCount; ++i )
9126 while ( nCurrTab < nCount && rTabStop[nCurrTab] < pInheritedTabs->At(i) )
9127 ++nCurrTab;
9129 if ( nCurrTab == nCount || pInheritedTabs->At(i) < rTabStop[nCurrTab] )
9131 m_pSerializer->singleElementNS( XML_w, XML_tab,
9132 FSNS( XML_w, XML_val ), "clear",
9133 FSNS( XML_w, XML_pos ), OString::number(pInheritedTabs->At(i).GetTabPos()) );
9137 for (sal_uInt16 i = 0; i < nCount; i++ )
9139 if( rTabStop[i].GetAdjustment() != SvxTabAdjust::Default )
9140 impl_WriteTabElement( m_pSerializer, rTabStop[i], tabsOffset );
9141 else
9142 GetExport().setDefaultTabStop( rTabStop[i].GetTabPos());
9145 m_pSerializer->endElementNS( XML_w, XML_tabs );
9148 void DocxAttributeOutput::ParaHyphenZone( const SvxHyphenZoneItem& rHyphenZone )
9150 m_pSerializer->singleElementNS( XML_w, XML_suppressAutoHyphens,
9151 FSNS( XML_w, XML_val ), OString::boolean( !rHyphenZone.IsHyphen() ) );
9154 void DocxAttributeOutput::ParaNumRule_Impl( const SwTextNode* pTextNd, sal_Int32 nLvl, sal_Int32 nNumId )
9156 if ( USHRT_MAX == nNumId )
9157 return;
9159 // LibreOffice is not very flexible with "Outline Numbering" (aka "Outline" numbering style).
9160 // Only ONE numbering rule ("Outline") can be associated with a style-assigned-listLevel,
9161 // and no other style is able to inherit these numId/nLvl settings - only text nodes can.
9162 // So listLevel only exists in paragraph properties EXCEPT for up to ten styles that have been
9163 // assigned to one of these special Chapter Numbering listlevels (by default Heading 1-10).
9164 const sal_Int32 nTableSize = m_rExport.m_pUsedNumTable ? m_rExport.m_pUsedNumTable->size() : 0;
9165 const SwNumRule* pRule = nNumId > 0 && nNumId <= nTableSize ? (*m_rExport.m_pUsedNumTable)[nNumId-1] : nullptr;
9166 const SwTextFormatColl* pColl = pTextNd ? pTextNd->GetTextColl() : nullptr;
9167 // Do not duplicate numbering that is inherited from the (Chapter numbering) style
9168 // (since on import we duplicate style numbering/listlevel to the paragraph).
9169 if (pColl && pColl->IsAssignedToListLevelOfOutlineStyle()
9170 && nLvl == pColl->GetAssignedOutlineStyleLevel() && pRule && pRule->IsOutlineRule())
9172 // By definition of how LO is implemented, assignToListLevel is only possible
9173 // when the style is also using OutlineRule for numbering. Adjust logic if that changes.
9174 assert(pRule->GetName() == pColl->GetNumRule(true).GetValue());
9175 return;
9178 m_pSerializer->startElementNS(XML_w, XML_numPr);
9179 m_pSerializer->singleElementNS(XML_w, XML_ilvl, FSNS(XML_w, XML_val), OString::number(nLvl));
9180 m_pSerializer->singleElementNS(XML_w, XML_numId, FSNS(XML_w, XML_val), OString::number(nNumId));
9181 m_pSerializer->endElementNS(XML_w, XML_numPr);
9184 void DocxAttributeOutput::ParaScriptSpace( const SfxBoolItem& rScriptSpace )
9186 m_pSerializer->singleElementNS( XML_w, XML_autoSpaceDE,
9187 FSNS( XML_w, XML_val ), OString::boolean( rScriptSpace.GetValue() ) );
9190 void DocxAttributeOutput::ParaHangingPunctuation( const SfxBoolItem& rItem )
9192 m_pSerializer->singleElementNS( XML_w, XML_overflowPunct,
9193 FSNS( XML_w, XML_val ), OString::boolean( rItem.GetValue() ) );
9196 void DocxAttributeOutput::ParaForbiddenRules( const SfxBoolItem& rItem )
9198 m_pSerializer->singleElementNS( XML_w, XML_kinsoku,
9199 FSNS( XML_w, XML_val ), OString::boolean( rItem.GetValue() ) );
9202 void DocxAttributeOutput::ParaVerticalAlign( const SvxParaVertAlignItem& rAlign )
9204 const char *pAlignString;
9206 switch ( rAlign.GetValue() )
9208 case SvxParaVertAlignItem::Align::Baseline:
9209 pAlignString = "baseline";
9210 break;
9211 case SvxParaVertAlignItem::Align::Top:
9212 pAlignString = "top";
9213 break;
9214 case SvxParaVertAlignItem::Align::Center:
9215 pAlignString = "center";
9216 break;
9217 case SvxParaVertAlignItem::Align::Bottom:
9218 pAlignString = "bottom";
9219 break;
9220 case SvxParaVertAlignItem::Align::Automatic:
9221 pAlignString = "auto";
9222 break;
9223 default:
9224 return; // not supported attribute
9226 m_pSerializer->singleElementNS(XML_w, XML_textAlignment, FSNS(XML_w, XML_val), pAlignString);
9229 void DocxAttributeOutput::ParaSnapToGrid( const SvxParaGridItem& rGrid )
9231 m_pSerializer->singleElementNS( XML_w, XML_snapToGrid,
9232 FSNS( XML_w, XML_val ), OString::boolean( rGrid.GetValue() ) );
9235 void DocxAttributeOutput::FormatFrameSize( const SwFormatFrameSize& rSize )
9237 if (m_rExport.SdrExporter().getTextFrameSyntax() && m_rExport.SdrExporter().getFlyFrameSize())
9239 const Size* pSize = m_rExport.SdrExporter().getFlyFrameSize();
9240 m_rExport.SdrExporter().getTextFrameStyle().append(";width:" + OString::number(double(pSize->Width()) / 20));
9241 m_rExport.SdrExporter().getTextFrameStyle().append("pt;height:" + OString::number(double(pSize->Height()) / 20) + "pt");
9243 else if (m_rExport.SdrExporter().getDMLTextFrameSyntax())
9246 else if ( m_rExport.m_bOutFlyFrameAttrs )
9248 if ( rSize.GetWidth() && rSize.GetWidthSizeType() == SwFrameSize::Fixed )
9249 AddToAttrList( m_rExport.SdrExporter().getFlyAttrList(),
9250 FSNS( XML_w, XML_w ), OString::number( rSize.GetWidth( ) ) );
9252 if ( rSize.GetHeight() )
9254 std::string_view sRule( "exact" );
9255 if ( rSize.GetHeightSizeType() == SwFrameSize::Minimum )
9256 sRule = "atLeast";
9257 AddToAttrList( m_rExport.SdrExporter().getFlyAttrList(),
9258 FSNS( XML_w, XML_hRule ), sRule,
9259 FSNS( XML_w, XML_h ), OString::number( rSize.GetHeight( ) ) );
9262 else if ( m_rExport.m_bOutPageDescs )
9264 rtl::Reference<FastAttributeList> attrList = FastSerializerHelper::createAttrList( );
9265 if ( m_rExport.m_pCurrentPageDesc->GetLandscape( ) )
9266 attrList->add( FSNS( XML_w, XML_orient ), "landscape" );
9268 attrList->add( FSNS( XML_w, XML_w ), OString::number( rSize.GetWidth( ) ) );
9269 attrList->add( FSNS( XML_w, XML_h ), OString::number( rSize.GetHeight( ) ) );
9271 m_pSerializer->singleElementNS( XML_w, XML_pgSz, attrList );
9275 void DocxAttributeOutput::FormatPaperBin(const SvxPaperBinItem& rPaperBin)
9277 sal_Int8 nPaperBin = rPaperBin.GetValue();
9278 rtl::Reference<FastAttributeList> attrList = FastSerializerHelper::createAttrList( );
9279 SfxPrinter* pPrinter = m_rExport.m_rDoc.getIDocumentDeviceAccess().getPrinter(true);
9280 sal_Int16 nPaperSource = pPrinter->GetSourceIndexByPaperBin(nPaperBin);
9281 attrList->add( FSNS( XML_w, XML_first ), OString::number(nPaperSource) );
9282 attrList->add( FSNS( XML_w, XML_other ), OString::number(nPaperSource) );
9283 m_pSerializer->singleElementNS( XML_w, XML_paperSrc, attrList );
9286 void DocxAttributeOutput::FormatFirstLineIndent(SvxFirstLineIndentItem const& rFirstLine)
9288 // tdf#83844: TODO: export FONT_CJK_ADVANCE first line indent as HangingChars/FirstLineChars
9289 sal_Int32 const nFirstLineAdjustment(rFirstLine.ResolveTextFirstLineOffset({}));
9290 if (nFirstLineAdjustment > 0)
9292 AddToAttrList(m_pLRSpaceAttrList, FSNS(XML_w, XML_firstLine),
9293 OString::number(nFirstLineAdjustment));
9295 else
9297 AddToAttrList(m_pLRSpaceAttrList, FSNS(XML_w, XML_hanging),
9298 OString::number(- nFirstLineAdjustment));
9302 void DocxAttributeOutput::FormatTextLeftMargin(SvxTextLeftMarginItem const& rTextLeftMargin)
9304 SvxTextLeftMarginItem const* pTextLeftMargin(&rTextLeftMargin);
9305 ::std::optional<SvxTextLeftMarginItem> oCopy;
9306 if (dynamic_cast<SwContentNode const*>(GetExport().m_pOutFormatNode) != nullptr)
9308 auto pTextNd(static_cast<SwTextNode const*>(GetExport().m_pOutFormatNode));
9309 // WW doesn't have a concept of a paragraph that's in a list but not
9310 // counted in the list - see AttributeOutputBase::ParaNumRule()
9311 // forcing non-existent numId="0" in this case.
9312 // This means WW won't apply the indents from the numbering,
9313 // so try to add them as paragraph properties here.
9314 if (!pTextNd->IsCountedInList())
9316 SfxItemSetFixed<RES_MARGIN_TEXTLEFT, RES_MARGIN_TEXTLEFT> temp(m_rExport.m_rDoc.GetAttrPool());
9317 pTextNd->GetParaAttr(temp, 0, 0, false, true, true, nullptr);
9318 if (auto *const pItem = temp.GetItem(RES_MARGIN_TEXTLEFT))
9320 oCopy.emplace(*pItem);
9321 pTextLeftMargin = &*oCopy;
9325 bool const bEcma1st(m_rExport.GetFilter().getVersion() == oox::core::ECMA_376_1ST_EDITION);
9326 AddToAttrList(m_pLRSpaceAttrList, FSNS(XML_w, (bEcma1st ? XML_left : XML_start)),
9327 OString::number(pTextLeftMargin->ResolveTextLeft({})));
9330 void DocxAttributeOutput::FormatRightMargin(SvxRightMarginItem const& rRightMargin)
9332 // (paragraph case, this will be an else branch once others are converted)
9333 #if 0
9334 else
9335 #endif
9337 bool const bEcma1st(m_rExport.GetFilter().getVersion() == oox::core::ECMA_376_1ST_EDITION);
9338 AddToAttrList(m_pLRSpaceAttrList, FSNS(XML_w, (bEcma1st ? XML_right : XML_end)),
9339 OString::number(rRightMargin.ResolveRight({})));
9343 void DocxAttributeOutput::FormatLRSpace( const SvxLRSpaceItem& rLRSpace )
9345 bool const bEcma = m_rExport.GetFilter().getVersion() == oox::core::ECMA_376_1ST_EDITION;
9346 if (m_rExport.SdrExporter().getTextFrameSyntax())
9348 m_rExport.SdrExporter().getTextFrameStyle().append(
9349 ";mso-wrap-distance-left:" + OString::number(double(rLRSpace.ResolveLeft({})) / 20)
9350 + "pt");
9351 m_rExport.SdrExporter().getTextFrameStyle().append(
9352 ";mso-wrap-distance-right:" + OString::number(double(rLRSpace.ResolveRight({})) / 20)
9353 + "pt");
9355 else if (m_rExport.SdrExporter().getDMLTextFrameSyntax())
9358 else if ( m_rExport.m_bOutFlyFrameAttrs )
9360 AddToAttrList(m_rExport.SdrExporter().getFlyAttrList(), FSNS(XML_w, XML_hSpace),
9361 OString::number((rLRSpace.ResolveLeft({}) + rLRSpace.ResolveRight({})) / 2));
9363 else if ( m_rExport.m_bOutPageDescs )
9365 m_pageMargins.nLeft = 0;
9366 m_pageMargins.nRight = 0;
9368 const SvxBoxItem* pBoxItem = m_rExport.HasItem(RES_BOX);
9369 if (pBoxItem)
9371 m_pageMargins.nLeft = pBoxItem->CalcLineSpace( SvxBoxItemLine::LEFT, /*bEvenIfNoLine*/true );
9372 m_pageMargins.nRight = pBoxItem->CalcLineSpace( SvxBoxItemLine::RIGHT, /*bEvenIfNoLine*/true );
9375 m_pageMargins.nLeft += sal::static_int_cast<sal_uInt16>(rLRSpace.ResolveLeft({}));
9376 m_pageMargins.nRight += sal::static_int_cast<sal_uInt16>(rLRSpace.ResolveRight({}));
9377 // if page layout is 'left' then left/right margin need to be exchanged
9378 // as it is exported as mirrored layout starting with even page
9379 const WW8_SepInfo *pSectionInfo = m_rExport.Sections().CurrentSectionInfo();
9380 if (pSectionInfo->pPageDesc &&
9381 m_rExport.isMirroredMargin() &&
9382 ((pSectionInfo->pPageDesc->ReadUseOn() & UseOnPage::All) == UseOnPage::Left))
9384 sal_uInt16 nLeft = m_pageMargins.nLeft;
9385 m_pageMargins.nLeft = m_pageMargins.nRight;
9386 m_pageMargins.nRight = nLeft;
9388 sal_uInt16 nGutter = rLRSpace.GetGutterMargin();
9390 AddToAttrList( m_pSectionSpacingAttrList,
9391 FSNS( XML_w, XML_left ), OString::number( m_pageMargins.nLeft ),
9392 FSNS( XML_w, XML_right ), OString::number( m_pageMargins.nRight ),
9393 FSNS( XML_w, XML_gutter ), OString::number( nGutter ) );
9395 else
9397 // note: this is not possible for SwTextNode but is for EditEngine!
9398 SvxLRSpaceItem const* pLRSpace(&rLRSpace);
9399 ::std::optional<SvxLRSpaceItem> oLRSpace;
9400 assert(dynamic_cast<SwContentNode const*>(GetExport().m_pOutFormatNode) == nullptr);
9401 rtl::Reference<FastAttributeList> pLRSpaceAttrList = FastSerializerHelper::createAttrList();
9402 if ((0 != pLRSpace->ResolveTextLeft({})) || (pLRSpace->IsExplicitZeroMarginValLeft()))
9404 pLRSpaceAttrList->add(FSNS(XML_w, (bEcma ? XML_left : XML_start)),
9405 OString::number(pLRSpace->ResolveTextLeft({})));
9407 if ((0 != pLRSpace->ResolveRight({})) || (pLRSpace->IsExplicitZeroMarginValRight()))
9409 pLRSpaceAttrList->add(FSNS(XML_w, (bEcma ? XML_right : XML_end)),
9410 OString::number(pLRSpace->ResolveRight({})));
9412 // tdf#83844: TODO: export FONT_CJK_ADVANCE first line indent as HangingChars/FirstLineChars
9413 sal_Int32 const nFirstLineAdjustment = pLRSpace->ResolveTextFirstLineOffset({});
9414 if (nFirstLineAdjustment > 0)
9415 pLRSpaceAttrList->add( FSNS( XML_w, XML_firstLine ), OString::number( nFirstLineAdjustment ) );
9416 else
9417 pLRSpaceAttrList->add( FSNS( XML_w, XML_hanging ), OString::number( - nFirstLineAdjustment ) );
9418 m_pSerializer->singleElementNS( XML_w, XML_ind, pLRSpaceAttrList );
9422 void DocxAttributeOutput::FormatULSpace( const SvxULSpaceItem& rULSpace )
9425 if (m_rExport.SdrExporter().getTextFrameSyntax())
9427 m_rExport.SdrExporter().getTextFrameStyle().append(";mso-wrap-distance-top:" + OString::number(double(rULSpace.GetUpper()) / 20) + "pt");
9428 m_rExport.SdrExporter().getTextFrameStyle().append(";mso-wrap-distance-bottom:" + OString::number(double(rULSpace.GetLower()) / 20) + "pt");
9430 else if (m_rExport.SdrExporter().getDMLTextFrameSyntax())
9433 else if ( m_rExport.m_bOutFlyFrameAttrs )
9435 AddToAttrList( m_rExport.SdrExporter().getFlyAttrList(), FSNS( XML_w, XML_vSpace ),
9436 OString::number(
9437 ( rULSpace.GetLower() + rULSpace.GetUpper() ) / 2 ) );
9439 else if (m_rExport.m_bOutPageDescs )
9441 OSL_ENSURE( m_rExport.GetCurItemSet(), "Impossible" );
9442 if ( !m_rExport.GetCurItemSet() )
9443 return;
9445 HdFtDistanceGlue aDistances( *m_rExport.GetCurItemSet() );
9447 sal_Int32 nHeader = 0;
9448 if ( aDistances.HasHeader() )
9449 nHeader = sal_Int32( aDistances.m_DyaHdrTop );
9450 else if (m_rExport.m_pFirstPageFormat)
9452 HdFtDistanceGlue aFirstPageDistances(m_rExport.m_pFirstPageFormat->GetAttrSet());
9453 if (aFirstPageDistances.HasHeader())
9455 // The follow page style has no header, but the first page style has. In Word terms,
9456 // this means that the header margin of "the" section is coming from the first page
9457 // style.
9458 nHeader = sal_Int32(aFirstPageDistances.m_DyaHdrTop);
9462 // Page top
9463 m_pageMargins.nTop = aDistances.m_DyaTop;
9465 sal_Int32 nFooter = 0;
9466 if ( aDistances.HasFooter() )
9467 nFooter = sal_Int32( aDistances.m_DyaHdrBottom );
9468 else if (m_rExport.m_pFirstPageFormat)
9470 HdFtDistanceGlue aFirstPageDistances(m_rExport.m_pFirstPageFormat->GetAttrSet());
9471 if (aFirstPageDistances.HasFooter())
9473 // The follow page style has no footer, but the first page style has. In Word terms,
9474 // this means that the footer margin of "the" section is coming from the first page
9475 // style.
9476 nFooter = sal_Int32(aFirstPageDistances.m_DyaHdrBottom);
9480 // Page Bottom
9481 m_pageMargins.nBottom = aDistances.m_DyaBottom;
9483 AddToAttrList( m_pSectionSpacingAttrList,
9484 FSNS( XML_w, XML_header ), OString::number( nHeader ),
9485 FSNS( XML_w, XML_top ), OString::number( m_pageMargins.nTop ),
9486 FSNS( XML_w, XML_footer ), OString::number( nFooter ),
9487 FSNS( XML_w, XML_bottom ), OString::number( m_pageMargins.nBottom ) );
9489 else
9491 SAL_INFO("sw.ww8", "DocxAttributeOutput::FormatULSpace: setting spacing" << rULSpace.GetUpper() );
9492 // check if before auto spacing was set during import and spacing we get from actual object is same
9493 // that we set in import. If yes just write beforeAutoSpacing tag.
9494 if (m_bParaBeforeAutoSpacing && m_nParaBeforeSpacing == rULSpace.GetUpper())
9496 AddToAttrList( m_pParagraphSpacingAttrList,
9497 FSNS( XML_w, XML_beforeAutospacing ), "1" );
9499 else if (m_bParaBeforeAutoSpacing && m_nParaBeforeSpacing == -1)
9501 AddToAttrList( m_pParagraphSpacingAttrList,
9502 FSNS( XML_w, XML_beforeAutospacing ), "0",
9503 FSNS( XML_w, XML_before ), OString::number( rULSpace.GetUpper() ) );
9505 else
9507 AddToAttrList( m_pParagraphSpacingAttrList,
9508 FSNS( XML_w, XML_before ), OString::number( rULSpace.GetUpper() ) );
9510 m_bParaBeforeAutoSpacing = false;
9511 // check if after auto spacing was set during import and spacing we get from actual object is same
9512 // that we set in import. If yes just write afterAutoSpacing tag.
9513 if (m_bParaAfterAutoSpacing && m_nParaAfterSpacing == rULSpace.GetLower())
9515 AddToAttrList( m_pParagraphSpacingAttrList,
9516 FSNS( XML_w, XML_afterAutospacing ), "1" );
9518 else if (m_bParaAfterAutoSpacing && m_nParaAfterSpacing == -1)
9520 AddToAttrList( m_pParagraphSpacingAttrList,
9521 FSNS( XML_w, XML_afterAutospacing ), "0",
9522 FSNS( XML_w, XML_after ), OString::number( rULSpace.GetLower()) );
9524 else
9526 AddToAttrList( m_pParagraphSpacingAttrList,
9527 FSNS( XML_w, XML_after ), OString::number( rULSpace.GetLower()) );
9529 m_bParaAfterAutoSpacing = false;
9531 if (rULSpace.GetContext())
9532 m_pSerializer->singleElementNS(XML_w, XML_contextualSpacing);
9533 else
9535 // Write out Contextual Spacing = false if it would have inherited a true.
9536 const SvxULSpaceItem* pInherited = nullptr;
9537 if (auto pNd = dynamic_cast<const SwContentNode*>(m_rExport.m_pOutFormatNode)) //paragraph
9538 pInherited = &static_cast<SwTextFormatColl&>(pNd->GetAnyFormatColl()).GetAttrSet().GetULSpace();
9539 else if (m_rExport.m_bStyDef && m_rExport.m_pCurrentStyle && m_rExport.m_pCurrentStyle->DerivedFrom()) //style
9540 pInherited = &m_rExport.m_pCurrentStyle->DerivedFrom()->GetULSpace();
9542 if (pInherited && pInherited->GetContext())
9543 m_pSerializer->singleElementNS(XML_w, XML_contextualSpacing, FSNS(XML_w, XML_val), "false");
9548 namespace docx {
9550 rtl::Reference<FastAttributeList> SurroundToVMLWrap(SwFormatSurround const& rSurround)
9552 std::string_view sType;
9553 std::string_view sSide;
9554 switch (rSurround.GetSurround())
9556 case css::text::WrapTextMode_NONE:
9557 sType = "topAndBottom";
9558 break;
9559 case css::text::WrapTextMode_PARALLEL:
9560 sType = "square";
9561 break;
9562 case css::text::WrapTextMode_DYNAMIC:
9563 sType = "square";
9564 sSide = "largest";
9565 break;
9566 case css::text::WrapTextMode_LEFT:
9567 sType = "square";
9568 sSide = "left";
9569 break;
9570 case css::text::WrapTextMode_RIGHT:
9571 sType = "square";
9572 sSide = "right";
9573 break;
9574 case css::text::WrapTextMode_THROUGH:
9575 /* empty type and side means through */
9576 default:
9577 sType = "none";
9578 break;
9580 rtl::Reference<FastAttributeList> pAttrList;
9581 if (!sType.empty())
9582 DocxAttributeOutput::AddToAttrList(pAttrList, XML_type, sType);
9583 if (!sSide.empty())
9584 DocxAttributeOutput::AddToAttrList(pAttrList, XML_side, sSide);
9585 return pAttrList;
9588 } // namespace docx
9590 void DocxAttributeOutput::FormatSurround( const SwFormatSurround& rSurround )
9592 if (m_rExport.SdrExporter().getTextFrameSyntax())
9594 rtl::Reference<FastAttributeList> pAttrList(docx::SurroundToVMLWrap(rSurround));
9595 if (pAttrList)
9597 m_rExport.SdrExporter().setFlyWrapAttrList(pAttrList);
9600 else if (m_rExport.SdrExporter().getDMLTextFrameSyntax())
9603 else if ( m_rExport.m_bOutFlyFrameAttrs )
9605 std::string_view sWrap;
9606 switch ( rSurround.GetSurround( ) )
9608 case css::text::WrapTextMode_NONE:
9609 sWrap = "none";
9610 break;
9611 case css::text::WrapTextMode_THROUGH:
9612 sWrap = "through";
9613 break;
9614 case css::text::WrapTextMode_DYNAMIC:
9615 case css::text::WrapTextMode_PARALLEL:
9616 case css::text::WrapTextMode_LEFT:
9617 case css::text::WrapTextMode_RIGHT:
9618 default:
9619 sWrap = "around";
9622 AddToAttrList( m_rExport.SdrExporter().getFlyAttrList(), FSNS( XML_w, XML_wrap ), sWrap );
9626 void DocxAttributeOutput::FormatVertOrientation( const SwFormatVertOrient& rFlyVert )
9628 OString sAlign = convertToOOXMLVertOrient( rFlyVert.GetVertOrient() );
9629 OString sVAnchor = convertToOOXMLVertOrientRel( rFlyVert.GetRelationOrient() );
9631 if (m_rExport.SdrExporter().getTextFrameSyntax())
9633 m_rExport.SdrExporter().getTextFrameStyle().append(";margin-top:" + OString::number(double(rFlyVert.GetPos()) / 20) + "pt");
9634 if ( !sAlign.isEmpty() )
9635 m_rExport.SdrExporter().getTextFrameStyle().append(";mso-position-vertical:" + sAlign);
9636 m_rExport.SdrExporter().getTextFrameStyle().append(";mso-position-vertical-relative:" + sVAnchor);
9638 else if (m_rExport.SdrExporter().getDMLTextFrameSyntax())
9641 else if ( m_rExport.m_bOutFlyFrameAttrs )
9643 if ( !sAlign.isEmpty() )
9644 AddToAttrList( m_rExport.SdrExporter().getFlyAttrList(), FSNS( XML_w, XML_yAlign ), sAlign );
9645 else
9646 AddToAttrList( m_rExport.SdrExporter().getFlyAttrList(), FSNS( XML_w, XML_y ),
9647 OString::number( rFlyVert.GetPos() ) );
9648 AddToAttrList( m_rExport.SdrExporter().getFlyAttrList(), FSNS( XML_w, XML_vAnchor ), sVAnchor );
9652 void DocxAttributeOutput::FormatHorizOrientation( const SwFormatHoriOrient& rFlyHori )
9654 OString sAlign = convertToOOXMLHoriOrient( rFlyHori.GetHoriOrient(), rFlyHori.IsPosToggle() );
9655 OString sHAnchor = convertToOOXMLHoriOrientRel( rFlyHori.GetRelationOrient() );
9657 if (m_rExport.SdrExporter().getTextFrameSyntax())
9659 m_rExport.SdrExporter().getTextFrameStyle().append(";margin-left:" + OString::number(double(rFlyHori.GetPos()) / 20) + "pt");
9660 if ( !sAlign.isEmpty() )
9661 m_rExport.SdrExporter().getTextFrameStyle().append(";mso-position-horizontal:" + sAlign);
9662 m_rExport.SdrExporter().getTextFrameStyle().append(";mso-position-horizontal-relative:" + sHAnchor);
9664 else if (m_rExport.SdrExporter().getDMLTextFrameSyntax())
9667 else if ( m_rExport.m_bOutFlyFrameAttrs )
9669 if ( !sAlign.isEmpty() )
9670 AddToAttrList( m_rExport.SdrExporter().getFlyAttrList(), FSNS( XML_w, XML_xAlign ), sAlign );
9671 else
9672 AddToAttrList( m_rExport.SdrExporter().getFlyAttrList(), FSNS( XML_w, XML_x ),
9673 OString::number( rFlyHori.GetPos() ) );
9674 AddToAttrList( m_rExport.SdrExporter().getFlyAttrList(), FSNS( XML_w, XML_hAnchor ), sHAnchor );
9678 void DocxAttributeOutput::FormatAnchor( const SwFormatAnchor& )
9680 // Fly frames: anchors here aren't matching the anchors in docx
9683 static std::optional<sal_Int32> lcl_getDmlAlpha(const SvxBrushItem& rBrush)
9685 std::optional<sal_Int32> oRet;
9686 sal_Int32 nTransparency = 255 - rBrush.GetColor().GetAlpha();
9687 if (nTransparency)
9689 // Convert transparency to percent
9690 sal_Int8 nTransparencyPercent = SvxBrushItem::TransparencyToPercent(nTransparency);
9692 // Calculate alpha value
9693 // Consider oox/source/drawingml/color.cxx : getTransparency() function.
9694 sal_Int32 nAlpha = ::oox::drawingml::MAX_PERCENT - ( ::oox::drawingml::PER_PERCENT * nTransparencyPercent );
9695 oRet = nAlpha;
9697 return oRet;
9700 void DocxAttributeOutput::FormatBackground( const SvxBrushItem& rBrush )
9702 const Color aColor = rBrush.GetColor();
9703 model::ComplexColor const& rComplexColor = rBrush.getComplexColor();
9704 OString sColor = msfilter::util::ConvertColor( aColor.GetRGBColor() );
9705 std::optional<sal_Int32> oAlpha = lcl_getDmlAlpha(rBrush);
9706 if (m_rExport.SdrExporter().getTextFrameSyntax())
9708 // Handle 'Opacity'
9709 if (oAlpha)
9711 // Calculate opacity value
9712 // Consider oox/source/vml/vmlformatting.cxx : decodeColor() function.
9713 double fOpacity = static_cast<double>(*oAlpha) * 65535 / ::oox::drawingml::MAX_PERCENT;
9715 AddToAttrList( m_rExport.SdrExporter().getFlyFillAttrList(), XML_opacity, OString::number(fOpacity) + "f" );
9718 AddToAttrList(m_rExport.SdrExporter().getFlyAttrList(), XML_fillcolor, "#" + sColor );
9719 lclAddThemeFillColorAttributes(m_rExport.SdrExporter().getFlyAttrList(), rComplexColor);
9721 else if (m_rExport.SdrExporter().getDMLTextFrameSyntax())
9723 bool bImageBackground = false;
9724 const SfxPoolItem* pItem = GetExport().HasItem(XATTR_FILLSTYLE);
9725 if (pItem)
9727 const XFillStyleItem* pFillStyle = static_cast<const XFillStyleItem*>(pItem);
9728 if(pFillStyle->GetValue() == drawing::FillStyle_BITMAP)
9730 bImageBackground = true;
9733 if (!bImageBackground)
9735 m_pSerializer->startElementNS(XML_a, XML_solidFill);
9736 m_pSerializer->startElementNS(XML_a, XML_srgbClr, XML_val, sColor);
9737 if (oAlpha)
9738 m_pSerializer->singleElementNS(XML_a, XML_alpha,
9739 XML_val, OString::number(*oAlpha));
9740 m_pSerializer->endElementNS(XML_a, XML_srgbClr);
9741 m_pSerializer->endElementNS(XML_a, XML_solidFill);
9744 else if ( !m_rExport.m_bOutPageDescs )
9746 // compare fill color with the original fill color
9747 OString sOriginalFill = OUStringToOString(
9748 m_sOriginalBackgroundColor, RTL_TEXTENCODING_UTF8 );
9750 if ( aColor == COL_AUTO )
9751 sColor = "auto"_ostr;
9753 if( !m_pBackgroundAttrList.is() )
9755 m_pBackgroundAttrList = FastSerializerHelper::createAttrList();
9756 m_pBackgroundAttrList->add(FSNS(XML_w, XML_fill), sColor);
9757 m_pBackgroundAttrList->add( FSNS( XML_w, XML_val ), "clear" );
9759 else if ( sOriginalFill != sColor )
9761 // fill was modified during edition, theme fill attribute must be dropped
9762 m_pBackgroundAttrList = FastSerializerHelper::createAttrList();
9763 m_pBackgroundAttrList->add(FSNS(XML_w, XML_fill), sColor);
9764 m_pBackgroundAttrList->add( FSNS( XML_w, XML_val ), "clear" );
9766 m_sOriginalBackgroundColor.clear();
9770 void DocxAttributeOutput::FormatFillStyle( const XFillStyleItem& rFillStyle )
9772 if (!m_bIgnoreNextFill)
9773 m_oFillStyle = rFillStyle.GetValue();
9774 else
9776 m_bIgnoreNextFill = false;
9777 // ITEM: Still need to signal that ::FormatFillStyle was called so that
9778 // ::FormatFillGradient does not assert but do nothing
9779 m_oFillStyle = drawing::FillStyle_NONE;
9782 // Don't round-trip grabbag OriginalBackground if the background has been cleared.
9783 if ( m_pBackgroundAttrList.is() && m_sOriginalBackgroundColor != "auto" && rFillStyle.GetValue() == drawing::FillStyle_NONE )
9784 m_pBackgroundAttrList.clear();
9787 void DocxAttributeOutput::FormatFillGradient( const XFillGradientItem& rFillGradient )
9789 assert(m_oFillStyle && "ITEM: FormatFillStyle *has* to be called before FormatFillGradient(!)");
9790 if (m_oFillStyle && *m_oFillStyle == drawing::FillStyle_GRADIENT && !m_rExport.SdrExporter().getDMLTextFrameSyntax())
9792 const basegfx::BGradient& rGradient = rFillGradient.GetGradientValue();
9793 OString sStartColor = msfilter::util::ConvertColor(Color(rGradient.GetColorStops().front().getStopColor()));
9794 OString sEndColor = msfilter::util::ConvertColor(Color(rGradient.GetColorStops().back().getStopColor()));
9796 const sal_Int32 nAngle = toDegrees(rGradient.GetAngle());
9797 if (nAngle != 0)
9798 AddToAttrList( m_rExport.SdrExporter().getFlyFillAttrList(),
9799 XML_angle, OString::number(nAngle));
9801 // LO does linear gradients top to bottom, while MSO does bottom to top.
9802 // LO does axial gradients inner to outer, while MSO does outer to inner.
9803 OString sColor1 = sEndColor; // LO end color is MSO start color
9804 OString sColor2 = sStartColor; // LO start color is MSO end color
9806 switch (rGradient.GetGradientStyle())
9808 case css::awt::GradientStyle_AXIAL:
9809 case css::awt::GradientStyle_LINEAR:
9811 bool bIsSymmetrical = rGradient.GetGradientStyle() == css::awt::GradientStyle_AXIAL;
9812 if (!bIsSymmetrical)
9814 const basegfx::BColorStops& rColorStops = rGradient.GetColorStops();
9815 if (rColorStops.size() > 2 && rColorStops.isSymmetrical())
9817 for (auto& rStop : rColorStops)
9819 if (basegfx::fTools::less(rStop.getStopOffset(), 0.5))
9820 continue;
9821 if (basegfx::fTools::more(rStop.getStopOffset(), 0.5))
9822 break;
9824 // from MSO export perspective, the inner color is the end color
9825 sColor2 = msfilter::util::ConvertColor(Color(rStop.getStopColor()));
9826 bIsSymmetrical = true;
9831 if (bIsSymmetrical)
9832 AddToAttrList( m_rExport.SdrExporter().getFlyFillAttrList(), XML_focus, "50%" );
9834 AddToAttrList(m_rExport.SdrExporter().getFlyFillAttrList(), XML_type, "gradient");
9835 break;
9837 case css::awt::GradientStyle_RADIAL:
9838 case css::awt::GradientStyle_ELLIPTICAL:
9839 case css::awt::GradientStyle_SQUARE:
9840 case css::awt::GradientStyle_RECT:
9841 AddToAttrList(m_rExport.SdrExporter().getFlyFillAttrList(), XML_type,
9842 "gradientRadial");
9843 // Since "focus" is not being written here, it defaults to 0.
9844 // A zero focus triggers a swap at LO import time, so a reverse swap is needed here.
9845 sColor1 = sStartColor;
9846 sColor2 = sEndColor;
9847 break;
9848 default:
9849 break;
9852 AddToAttrList( m_rExport.SdrExporter().getFlyAttrList(), XML_fillcolor, "#" + sColor1 );
9853 AddToAttrList( m_rExport.SdrExporter().getFlyFillAttrList(), XML_color2, "#" + sColor2 );
9855 else if (m_oFillStyle && *m_oFillStyle == drawing::FillStyle_GRADIENT && m_rExport.SdrExporter().getDMLTextFrameSyntax())
9857 SwFrameFormat & rFormat(
9858 const_cast<SwFrameFormat&>(m_rExport.m_pParentFrame->GetFrameFormat()));
9859 rtl::Reference<SwXTextFrame> const xPropertySet =
9860 SwXTextFrame::CreateXTextFrame(*rFormat.GetDoc(), &rFormat);
9861 m_rDrawingML.SetFS(m_pSerializer);
9862 m_rDrawingML.WriteGradientFill(uno::Reference<beans::XPropertySet>(static_cast<SwXFrame*>(xPropertySet.get())));
9864 m_oFillStyle.reset();
9867 void DocxAttributeOutput::FormatBox( const SvxBoxItem& rBox )
9869 if (m_rExport.SdrExporter().getDMLTextFrameSyntax())
9871 // ugh, exporting fill here is quite some hack... this OutputItemSet abstraction is quite leaky
9872 // <a:gradFill> should be before <a:ln>.
9873 const SfxPoolItem* pItem = GetExport().HasItem(XATTR_FILLSTYLE);
9874 if (pItem)
9876 const XFillStyleItem* pFillStyle = static_cast<const XFillStyleItem*>(pItem);
9877 FormatFillStyle(*pFillStyle);
9878 if (m_oFillStyle && *m_oFillStyle == drawing::FillStyle_BITMAP)
9880 const SdrObject* pSdrObj = m_rExport.m_pParentFrame->GetFrameFormat().FindRealSdrObject();
9881 if (pSdrObj)
9883 uno::Reference< drawing::XShape > xShape( const_cast<SdrObject*>(pSdrObj)->getUnoShape(), uno::UNO_QUERY );
9884 uno::Reference< beans::XPropertySet > xPropertySet( xShape, uno::UNO_QUERY );
9885 m_rDrawingML.SetFS(m_pSerializer);
9886 m_rDrawingML.WriteBlipFill(xPropertySet, u"BackGraphic"_ustr);
9891 pItem = GetExport().HasItem(XATTR_FILLGRADIENT);
9892 if (pItem)
9894 const XFillGradientItem* pFillGradient = static_cast<const XFillGradientItem*>(pItem);
9895 FormatFillGradient(*pFillGradient);
9897 m_bIgnoreNextFill = true;
9899 if (m_rExport.SdrExporter().getTextFrameSyntax() || m_rExport.SdrExporter().getDMLTextFrameSyntax())
9901 const SvxBorderLine* pLeft = rBox.GetLeft( );
9902 const SvxBorderLine* pTop = rBox.GetTop( );
9903 const SvxBorderLine* pRight = rBox.GetRight( );
9904 const SvxBorderLine* pBottom = rBox.GetBottom( );
9906 if (pLeft && pRight && pTop && pBottom &&
9907 *pLeft == *pRight && *pLeft == *pTop && *pLeft == *pBottom)
9909 // Check border style
9910 SvxBorderLineStyle eBorderStyle = pTop->GetBorderLineStyle();
9911 if (eBorderStyle == SvxBorderLineStyle::NONE)
9913 if (m_rExport.SdrExporter().getTextFrameSyntax())
9915 AddToAttrList( m_rExport.SdrExporter().getFlyAttrList(),
9916 XML_stroked, "f", XML_strokeweight, "0pt" );
9919 else
9921 OString sColor(msfilter::util::ConvertColor(pTop->GetColor()));
9922 double const fConverted(editeng::ConvertBorderWidthToWord(pTop->GetBorderLineStyle(), pTop->GetWidth()));
9924 if (m_rExport.SdrExporter().getTextFrameSyntax())
9926 sal_Int32 nWidth = sal_Int32(fConverted / 20);
9927 AddToAttrList( m_rExport.SdrExporter().getFlyAttrList(),
9928 XML_strokecolor, "#" + sColor,
9929 XML_strokeweight, OString::number(nWidth) + "pt" );
9930 if( SvxBorderLineStyle::DASHED == pTop->GetBorderLineStyle() ) // Line Style is Dash type
9931 AddToAttrList( m_rExport.SdrExporter().getDashLineStyle(),
9932 XML_dashstyle, "dash" );
9934 else
9935 m_rExport.SdrExporter().writeBoxItemLine(rBox);
9939 if (m_rExport.SdrExporter().getDMLTextFrameSyntax())
9941 m_rExport.SdrExporter().getBodyPrAttrList()->add(XML_lIns, OString::number(TwipsToEMU(rBox.GetDistance(SvxBoxItemLine::LEFT))));
9942 m_rExport.SdrExporter().getBodyPrAttrList()->add(XML_tIns, OString::number(TwipsToEMU(rBox.GetDistance(SvxBoxItemLine::TOP))));
9943 m_rExport.SdrExporter().getBodyPrAttrList()->add(XML_rIns, OString::number(TwipsToEMU(rBox.GetDistance(SvxBoxItemLine::RIGHT))));
9944 m_rExport.SdrExporter().getBodyPrAttrList()->add(XML_bIns, OString::number(TwipsToEMU(rBox.GetDistance(SvxBoxItemLine::BOTTOM))));
9945 return;
9948 // v:textbox's inset attribute: inner margin values for textbox text - write only non-default values
9949 double fDistanceLeftTwips = double(rBox.GetDistance(SvxBoxItemLine::LEFT));
9950 double fDistanceTopTwips = double(rBox.GetDistance(SvxBoxItemLine::TOP));
9951 double fDistanceRightTwips = double(rBox.GetDistance(SvxBoxItemLine::RIGHT));
9952 double fDistanceBottomTwips = double(rBox.GetDistance(SvxBoxItemLine::BOTTOM));
9954 // Convert 'TWIPS' to 'INCH' (because in Word the default values are in Inches)
9955 double fDistanceLeftInch = o3tl::convert(fDistanceLeftTwips, o3tl::Length::twip, o3tl::Length::in);
9956 double fDistanceTopInch = o3tl::convert(fDistanceTopTwips, o3tl::Length::twip, o3tl::Length::in);
9957 double fDistanceRightInch = o3tl::convert(fDistanceRightTwips, o3tl::Length::twip, o3tl::Length::in);
9958 double fDistanceBottomInch = o3tl::convert(fDistanceBottomTwips, o3tl::Length::twip, o3tl::Length::in);
9960 // This code will write ONLY the non-default values. The values are in 'left','top','right','bottom' order.
9961 // so 'bottom' is checked if it is default and if it is non-default - all the values will be written
9962 // otherwise - 'right' is checked if it is default and if it is non-default - all the values except for 'bottom' will be written
9963 // and so on.
9964 OStringBuffer aInset;
9965 if(!aInset.isEmpty() || fDistanceBottomInch != 0.05)
9966 aInset.insert(0, Concat2View("," + OString::number(fDistanceBottomInch) + "in"));
9968 if(!aInset.isEmpty() || fDistanceRightInch != 0.1)
9969 aInset.insert(0, Concat2View("," + OString::number(fDistanceRightInch) + "in"));
9971 if(!aInset.isEmpty() || fDistanceTopInch != 0.05)
9972 aInset.insert(0, Concat2View("," + OString::number(fDistanceTopInch) + "in"));
9974 if(!aInset.isEmpty() || fDistanceLeftInch != 0.1)
9975 aInset.insert(0, Concat2View(OString::number(fDistanceLeftInch) + "in"));
9977 if (!aInset.isEmpty())
9978 m_rExport.SdrExporter().getTextboxAttrList()->add(XML_inset, aInset);
9980 return;
9983 OutputBorderOptions aOutputBorderOptions = lcl_getBoxBorderOptions();
9984 // Check if there is a shadow item
9985 const SfxPoolItem* pItem = GetExport().HasItem( RES_SHADOW );
9986 if ( pItem )
9988 const SvxShadowItem* pShadowItem = static_cast<const SvxShadowItem*>(pItem);
9989 aOutputBorderOptions.aShadowLocation = pShadowItem->GetLocation();
9992 if ( m_bOpenedSectPr && !GetWritingHeaderFooter())
9993 return;
9995 // Not inside a section
9997 // Open the paragraph's borders tag
9998 m_pSerializer->startElementNS(XML_w, XML_pBdr);
10000 std::map<SvxBoxItemLine, css::table::BorderLine2> aStyleBorders;
10001 const SvxBoxItem* pInherited = nullptr;
10002 if ( GetExport().m_pStyAttr )
10003 pInherited = GetExport().m_pStyAttr->GetItem<SvxBoxItem>(RES_BOX);
10004 else if ( GetExport().m_pCurrentStyle && GetExport().m_pCurrentStyle->DerivedFrom() )
10005 pInherited = GetExport().m_pCurrentStyle->DerivedFrom()->GetAttrSet().GetItem<SvxBoxItem>(RES_BOX);
10007 if ( pInherited )
10009 aStyleBorders[ SvxBoxItemLine::TOP ] = SvxBoxItem::SvxLineToLine(pInherited->GetTop(), /*bConvert=*/false);
10010 aStyleBorders[ SvxBoxItemLine::BOTTOM ] = SvxBoxItem::SvxLineToLine(pInherited->GetBottom(), false);
10011 aStyleBorders[ SvxBoxItemLine::LEFT ] = SvxBoxItem::SvxLineToLine(pInherited->GetLeft(), false);
10012 aStyleBorders[ SvxBoxItemLine::RIGHT ] = SvxBoxItem::SvxLineToLine(pInherited->GetRight(), false);
10014 bool bUseFrame = m_aFramePr.UseFrameBorders(!m_xTableWrt ? -1 : m_tableReference.m_nTableDepth);
10015 impl_borders(m_pSerializer, rBox, aOutputBorderOptions, aStyleBorders,
10016 bUseFrame ? m_aFramePr.Frame() : nullptr);
10018 // Close the paragraph's borders tag
10019 m_pSerializer->endElementNS( XML_w, XML_pBdr );
10021 m_aFramePr.SetUseFrameBorders(false);
10024 void DocxAttributeOutput::FormatColumns_Impl( sal_uInt16 nCols, const SwFormatCol& rCol, bool bEven, SwTwips nPageSize )
10026 // Get the columns attributes
10027 rtl::Reference<FastAttributeList> pColsAttrList = FastSerializerHelper::createAttrList();
10029 pColsAttrList->add( FSNS( XML_w, XML_num ), OString::number( nCols ) );
10031 std::string_view pEquals = "false";
10032 if ( bEven )
10034 sal_uInt16 nWidth = rCol.GetGutterWidth( true );
10035 pColsAttrList->add( FSNS( XML_w, XML_space ), OString::number( nWidth ) );
10037 pEquals = "true";
10040 pColsAttrList->add( FSNS( XML_w, XML_equalWidth ), pEquals );
10042 bool bHasSep = (COLADJ_NONE != rCol.GetLineAdj());
10044 pColsAttrList->add( FSNS( XML_w, XML_sep ), OString::boolean( bHasSep ) );
10046 // Write the element
10047 m_pSerializer->startElementNS( XML_w, XML_cols, pColsAttrList );
10049 // Write the columns width if non-equals
10050 const SwColumns & rColumns = rCol.GetColumns( );
10051 if ( !bEven )
10053 for ( sal_uInt16 n = 0; n < nCols; ++n )
10055 rtl::Reference<FastAttributeList> pColAttrList = FastSerializerHelper::createAttrList();
10056 sal_uInt16 nWidth = rCol.CalcPrtColWidth( n, o3tl::narrowing<sal_uInt16>(nPageSize) );
10057 pColAttrList->add( FSNS( XML_w, XML_w ), OString::number( nWidth ) );
10059 if ( n + 1 != nCols )
10061 sal_uInt16 nSpacing = rColumns[n].GetRight( ) + rColumns[n + 1].GetLeft( );
10062 pColAttrList->add( FSNS( XML_w, XML_space ), OString::number( nSpacing ) );
10065 m_pSerializer->singleElementNS( XML_w, XML_col, pColAttrList );
10069 m_pSerializer->endElementNS( XML_w, XML_cols );
10072 void DocxAttributeOutput::FormatKeep( const SvxFormatKeepItem& rItem )
10074 m_pSerializer->singleElementNS( XML_w, XML_keepNext,
10075 FSNS( XML_w, XML_val ), OString::boolean( rItem.GetValue() ) );
10078 void DocxAttributeOutput::FormatTextGrid( const SwTextGridItem& rGrid )
10080 rtl::Reference<FastAttributeList> pGridAttrList = FastSerializerHelper::createAttrList();
10082 std::string_view sGridType;
10083 switch ( rGrid.GetGridType( ) )
10085 default:
10086 case GRID_NONE:
10087 sGridType = "default";
10088 break;
10089 case GRID_LINES_ONLY:
10090 sGridType = "lines";
10091 break;
10092 case GRID_LINES_CHARS:
10093 if ( rGrid.IsSnapToChars( ) )
10094 sGridType = "snapToChars";
10095 else
10096 sGridType = "linesAndChars";
10097 break;
10099 pGridAttrList->add(FSNS(XML_w, XML_type), sGridType);
10101 sal_uInt16 nHeight = rGrid.GetBaseHeight() + rGrid.GetRubyHeight();
10102 pGridAttrList->add( FSNS( XML_w, XML_linePitch ),
10103 OString::number( nHeight ) );
10105 pGridAttrList->add( FSNS( XML_w, XML_charSpace ),
10106 OString::number( GridCharacterPitch( rGrid ) ) );
10108 m_pSerializer->singleElementNS( XML_w, XML_docGrid, pGridAttrList );
10111 void DocxAttributeOutput::FormatLineNumbering( const SwFormatLineNumber& rNumbering )
10113 if ( !rNumbering.IsCount( ) )
10114 m_pSerializer->singleElementNS(XML_w, XML_suppressLineNumbers);
10115 else
10116 m_pSerializer->singleElementNS(XML_w, XML_suppressLineNumbers, FSNS(XML_w, XML_val), "0");
10119 void DocxAttributeOutput::FormatFrameDirection( const SvxFrameDirectionItem& rDirection )
10121 OString sTextFlow;
10122 bool bBiDi = false;
10123 SvxFrameDirection nDir = rDirection.GetValue();
10125 if ( nDir == SvxFrameDirection::Environment )
10126 nDir = GetExport( ).GetDefaultFrameDirection( );
10128 switch ( nDir )
10130 default:
10131 case SvxFrameDirection::Horizontal_LR_TB:
10132 sTextFlow = "lrTb"_ostr;
10133 break;
10134 case SvxFrameDirection::Horizontal_RL_TB:
10135 sTextFlow = "lrTb"_ostr;
10136 bBiDi = true;
10137 break;
10138 case SvxFrameDirection::Vertical_LR_TB: // ~ vert="mongolianVert"
10139 sTextFlow = "tbLrV"_ostr;
10140 break;
10141 case SvxFrameDirection::Vertical_RL_TB: // ~ vert="eaVert"
10142 sTextFlow = "tbRl"_ostr;
10143 break;
10144 case SvxFrameDirection::Vertical_LR_BT: // ~ vert="vert270"
10145 sTextFlow = "btLr"_ostr;
10146 break;
10147 case SvxFrameDirection::Vertical_RL_TB90: // ~ vert="vert"
10148 sTextFlow = "tbRlV"_ostr;
10149 break;
10152 if ( m_rExport.m_bOutPageDescs )
10154 m_pSerializer->singleElementNS(XML_w, XML_textDirection, FSNS(XML_w, XML_val), sTextFlow);
10155 if ( bBiDi )
10156 m_pSerializer->singleElementNS(XML_w, XML_bidi);
10158 else if ( !m_rExport.m_bOutFlyFrameAttrs )
10160 if ( bBiDi )
10161 m_pSerializer->singleElementNS(XML_w, XML_bidi, FSNS(XML_w, XML_val), "1");
10162 else
10163 m_pSerializer->singleElementNS(XML_w, XML_bidi, FSNS(XML_w, XML_val), "0");
10164 m_aFramePr.SetUseFrameTextDirection(false);
10168 void DocxAttributeOutput::ParaGrabBag(const SfxGrabBagItem& rItem)
10170 const std::map<OUString, css::uno::Any>& rMap = rItem.GetGrabBag();
10171 for ( const auto & rGrabBagElement : rMap )
10173 if (rGrabBagElement.first == "MirrorIndents")
10174 m_pSerializer->singleElementNS(XML_w, XML_mirrorIndents);
10175 else if (rGrabBagElement.first == "ParaTopMarginBeforeAutoSpacing")
10177 m_bParaBeforeAutoSpacing = true;
10178 // get fixed value which was set during import
10179 rGrabBagElement.second >>= m_nParaBeforeSpacing;
10180 m_nParaBeforeSpacing = o3tl::toTwips(m_nParaBeforeSpacing, o3tl::Length::mm100);
10181 SAL_INFO("sw.ww8", "DocxAttributeOutput::ParaGrabBag: property =" << rGrabBagElement.first << " : m_nParaBeforeSpacing= " << m_nParaBeforeSpacing);
10183 else if (rGrabBagElement.first == "ParaBottomMarginAfterAutoSpacing")
10185 m_bParaAfterAutoSpacing = true;
10186 // get fixed value which was set during import
10187 rGrabBagElement.second >>= m_nParaAfterSpacing;
10188 m_nParaAfterSpacing = o3tl::toTwips(m_nParaAfterSpacing, o3tl::Length::mm100);
10189 SAL_INFO("sw.ww8", "DocxAttributeOutput::ParaGrabBag: property =" << rGrabBagElement.first << " : m_nParaBeforeSpacing= " << m_nParaAfterSpacing);
10191 else if (rGrabBagElement.first == "CharThemeFill")
10193 uno::Sequence<beans::PropertyValue> aGrabBagSeq;
10194 rGrabBagElement.second >>= aGrabBagSeq;
10196 for (const auto& rProp : aGrabBagSeq)
10198 OUString sVal = rProp.Value.get<OUString>();
10200 if (sVal.isEmpty())
10201 continue;
10203 if (rProp.Name == "val")
10204 AddToAttrList(m_pBackgroundAttrList, FSNS(XML_w, XML_val), sVal);
10205 else if (rProp.Name == "color")
10206 AddToAttrList(m_pBackgroundAttrList, FSNS(XML_w, XML_color), sVal);
10207 else if (rProp.Name == "themeColor")
10208 AddToAttrList(m_pBackgroundAttrList, FSNS(XML_w, XML_themeColor), sVal);
10209 else if (rProp.Name == "themeTint")
10210 AddToAttrList(m_pBackgroundAttrList, FSNS(XML_w, XML_themeTint), sVal);
10211 else if (rProp.Name == "themeShade")
10212 AddToAttrList(m_pBackgroundAttrList, FSNS(XML_w, XML_themeShade), sVal);
10213 else if (rProp.Name == "fill")
10214 AddToAttrList(m_pBackgroundAttrList, FSNS(XML_w, XML_fill), sVal);
10215 else if (rProp.Name == "themeFill")
10216 AddToAttrList(m_pBackgroundAttrList, FSNS(XML_w, XML_themeFill), sVal);
10217 else if (rProp.Name == "themeFillTint")
10218 AddToAttrList(m_pBackgroundAttrList, FSNS(XML_w, XML_themeFillTint), sVal);
10219 else if (rProp.Name == "themeFillShade")
10220 AddToAttrList(m_pBackgroundAttrList, FSNS(XML_w, XML_themeFillShade), sVal);
10221 else if (rProp.Name == "originalColor")
10222 rProp.Value >>= m_sOriginalBackgroundColor;
10225 else if (rGrabBagElement.first == "SdtPr")
10227 const uno::Sequence<beans::PropertyValue> aGrabBagSdt =
10228 rGrabBagElement.second.get< uno::Sequence<beans::PropertyValue> >();
10229 m_aParagraphSdt.GetSdtParamsFromGrabBag(aGrabBagSdt);
10230 m_aStartedParagraphSdtPrAlias = m_aParagraphSdt.m_aAlias;
10232 else if (rGrabBagElement.first == "ParaCnfStyle")
10234 uno::Sequence<beans::PropertyValue> aAttributes = rGrabBagElement.second.get< uno::Sequence<beans::PropertyValue> >();
10235 m_pTableStyleExport->CnfStyle(aAttributes);
10237 else if (rGrabBagElement.first == "ParaSdtEndBefore")
10239 // Handled already in StartParagraph().
10241 else if (rGrabBagElement.first == "ParaInlineHeading")
10242 m_rExport.m_bParaInlineHeading = true;
10243 else
10244 SAL_WARN("sw.ww8", "DocxAttributeOutput::ParaGrabBag: unhandled grab bag property " << rGrabBagElement.first );
10248 void DocxAttributeOutput::CharGrabBag( const SfxGrabBagItem& rItem )
10250 if (m_bPreventDoubleFieldsHandling)
10251 return;
10253 const std::map< OUString, css::uno::Any >& rMap = rItem.GetGrabBag();
10255 // get original values of theme-derived properties to check if they have changed during the edition
10256 bool bWriteCSTheme = true;
10257 bool bWriteAsciiTheme = true;
10258 bool bWriteEastAsiaTheme = true;
10259 OUString sOriginalValue;
10260 for ( const auto & rGrabBagElement : rMap )
10262 if ( m_pFontsAttrList.is() && rGrabBagElement.first == "CharThemeFontNameCs" )
10264 if ( rGrabBagElement.second >>= sOriginalValue )
10265 bWriteCSTheme =
10266 ( m_pFontsAttrList->getOptionalValue( FSNS( XML_w, XML_cs ) ) == sOriginalValue );
10268 else if ( m_pFontsAttrList.is() && rGrabBagElement.first == "CharThemeFontNameAscii" )
10270 if ( rGrabBagElement.second >>= sOriginalValue )
10271 bWriteAsciiTheme =
10272 ( m_pFontsAttrList->getOptionalValue( FSNS( XML_w, XML_ascii ) ) == sOriginalValue );
10274 else if ( m_pFontsAttrList.is() && rGrabBagElement.first == "CharThemeFontNameEastAsia" )
10276 if ( rGrabBagElement.second >>= sOriginalValue )
10277 bWriteEastAsiaTheme =
10278 ( m_pFontsAttrList->getOptionalValue( FSNS( XML_w, XML_eastAsia ) ) == sOriginalValue );
10282 // save theme attributes back to the run properties
10283 OUString str;
10284 for ( const auto & rGrabBagElement : rMap )
10286 if ( rGrabBagElement.first == "CharThemeNameAscii" && bWriteAsciiTheme )
10288 rGrabBagElement.second >>= str;
10289 AddToAttrList( m_pFontsAttrList, FSNS( XML_w, XML_asciiTheme ), str );
10291 else if ( rGrabBagElement.first == "CharThemeNameCs" && bWriteCSTheme )
10293 rGrabBagElement.second >>= str;
10294 AddToAttrList( m_pFontsAttrList, FSNS( XML_w, XML_cstheme ), str );
10296 else if ( rGrabBagElement.first == "CharThemeNameEastAsia" && bWriteEastAsiaTheme )
10298 rGrabBagElement.second >>= str;
10299 AddToAttrList( m_pFontsAttrList, FSNS( XML_w, XML_eastAsiaTheme ), str );
10301 else if ( rGrabBagElement.first == "CharThemeNameHAnsi" && bWriteAsciiTheme )
10302 // this is not a mistake: in LibO we don't directly support the hAnsi family
10303 // of attributes so we save the same value from ascii attributes instead
10305 rGrabBagElement.second >>= str;
10306 AddToAttrList( m_pFontsAttrList, FSNS( XML_w, XML_hAnsiTheme ), str );
10308 else if( rGrabBagElement.first == "CharThemeFontNameCs" ||
10309 rGrabBagElement.first == "CharThemeFontNameAscii" ||
10310 rGrabBagElement.first == "CharThemeFontNameEastAsia" ||
10311 rGrabBagElement.first == "CharThemeOriginalColor" )
10313 // just skip these, they were processed before
10315 else if(rGrabBagElement.first == "CharGlowTextEffect" ||
10316 rGrabBagElement.first == "CharShadowTextEffect" ||
10317 rGrabBagElement.first == "CharReflectionTextEffect" ||
10318 rGrabBagElement.first == "CharTextOutlineTextEffect" ||
10319 rGrabBagElement.first == "CharScene3DTextEffect" ||
10320 rGrabBagElement.first == "CharProps3DTextEffect" ||
10321 rGrabBagElement.first == "CharLigaturesTextEffect" ||
10322 rGrabBagElement.first == "CharNumFormTextEffect" ||
10323 rGrabBagElement.first == "CharNumSpacingTextEffect" ||
10324 rGrabBagElement.first == "CharStylisticSetsTextEffect" ||
10325 rGrabBagElement.first == "CharCntxtAltsTextEffect")
10327 beans::PropertyValue aPropertyValue;
10328 rGrabBagElement.second >>= aPropertyValue;
10329 m_aTextEffectsGrabBag.push_back(aPropertyValue);
10331 else if (rGrabBagElement.first == "CharTextFillTextEffect")
10333 beans::PropertyValue aPropertyValue;
10334 rGrabBagElement.second >>= aPropertyValue;
10335 m_aTextFillGrabBag.push_back(aPropertyValue);
10337 else if (rGrabBagElement.first == "SdtEndBefore")
10339 if (m_aRunSdt.m_bStartedSdt)
10340 m_bEndCharSdt = true;
10342 else if (rGrabBagElement.first == "SdtPr" && FLY_NOT_PROCESSED != m_nStateOfFlyFrame )
10344 const uno::Sequence<beans::PropertyValue> aGrabBagSdt =
10345 rGrabBagElement.second.get< uno::Sequence<beans::PropertyValue> >();
10346 m_aRunSdt.GetSdtParamsFromGrabBag(aGrabBagSdt);
10348 else
10349 SAL_INFO("sw.ww8", "DocxAttributeOutput::CharGrabBag: unhandled grab bag property " << rGrabBagElement.first);
10353 DocxAttributeOutput::DocxAttributeOutput( DocxExport &rExport, const FSHelperPtr& pSerializer, oox::drawingml::DrawingML* pDrawingML )
10354 : AttributeOutputBase(rExport.GetFilter().getFileUrl()),
10355 m_rExport( rExport ),
10356 m_pSerializer( pSerializer ),
10357 m_rDrawingML( *pDrawingML ),
10358 m_bEndCharSdt(false),
10359 m_endPageRef( false ),
10360 m_pFootnotesList( new ::docx::FootnotesList() ),
10361 m_pEndnotesList( new ::docx::FootnotesList() ),
10362 m_footnoteEndnoteRefTag( 0 ),
10363 m_pRedlineData( nullptr ),
10364 m_nRedlineId( 0 ),
10365 m_bOpenedSectPr( false ),
10366 m_bHadSectPr(false),
10367 m_bOpenedParaPr( false ),
10368 m_bRunTextIsOn( false ),
10369 m_bWritingHeaderFooter( false ),
10370 m_bAnchorLinkedToNode(false),
10371 m_bWritingField( false ),
10372 m_bPreventDoubleFieldsHandling( false ),
10373 m_nNextBookmarkId( 0 ),
10374 m_nNextAnnotationMarkId( 0 ),
10375 m_nEmbedFlyLevel(0),
10376 m_pMoveRedlineData(nullptr),
10377 m_bParagraphOpened( false ),
10378 m_bParagraphFrameOpen( false ),
10379 m_bIsFirstParagraph( true ),
10380 m_bAlternateContentChoiceOpen( false ),
10381 m_bPostponedProcessingFly( false ),
10382 m_nColBreakStatus( COLBRK_NONE ),
10383 m_bPostponedPageBreak( false ),
10384 m_nTextFrameLevel( 0 ),
10385 m_closeHyperlinkInThisRun( false ),
10386 m_closeHyperlinkInPreviousRun( false ),
10387 m_nFieldsInHyperlink( 0 ),
10388 m_bExportingOutline(false),
10389 m_nChartCount(0),
10390 m_PendingPlaceholder( nullptr ),
10391 m_postitFieldsMaxId( 0 ),
10392 m_anchorId( 1 ),
10393 m_nextFontId( 1 ),
10394 m_bIgnoreNextFill(false),
10395 m_pTableStyleExport(std::make_shared<DocxTableStyleExport>(rExport.m_rDoc, pSerializer)),
10396 m_bParaBeforeAutoSpacing(false),
10397 m_bParaAfterAutoSpacing(false),
10398 m_nParaBeforeSpacing(0),
10399 m_nParaAfterSpacing(0),
10400 m_bParaInlineHeading(false)
10401 , m_nStateOfFlyFrame( FLY_NOT_PROCESSED )
10403 m_nHyperLinkCount.push_back(0);
10406 DocxAttributeOutput::~DocxAttributeOutput()
10410 DocxExport& DocxAttributeOutput::GetExport()
10412 return m_rExport;
10415 void DocxAttributeOutput::SetSerializer( ::sax_fastparser::FSHelperPtr const & pSerializer )
10417 m_pSerializer = pSerializer;
10418 m_pTableStyleExport->SetSerializer(pSerializer);
10421 bool DocxAttributeOutput::HasFootnotes() const
10423 return !m_pFootnotesList->isEmpty();
10426 bool DocxAttributeOutput::HasEndnotes() const
10428 return !m_pEndnotesList->isEmpty();
10431 bool DocxAttributeOutput::HasPostitFields() const
10433 return !m_postitFields.empty();
10436 void DocxAttributeOutput::BulletDefinition(int nId, const Graphic& rGraphic, Size aSize)
10438 m_pSerializer->startElementNS(XML_w, XML_numPicBullet,
10439 FSNS(XML_w, XML_numPicBulletId), OString::number(nId));
10441 // Size is in twips, we need it in points.
10442 OString aStyle = "width:" + OString::number(double(aSize.Width()) / 20)+ "pt;"
10443 "height:" + OString::number(double(aSize.Height()) / 20) + "pt";
10444 m_pSerializer->startElementNS(XML_w, XML_pict);
10445 m_pSerializer->startElementNS( XML_v, XML_shape,
10446 XML_style, aStyle,
10447 FSNS(XML_o, XML_bullet), "t");
10449 OUString aRelId = m_rDrawingML.writeGraphicToStorage(rGraphic);
10450 m_pSerializer->singleElementNS( XML_v, XML_imagedata,
10451 FSNS(XML_r, XML_id), aRelId,
10452 FSNS(XML_o, XML_title), "");
10454 m_pSerializer->endElementNS(XML_v, XML_shape);
10455 m_pSerializer->endElementNS(XML_w, XML_pict);
10457 m_pSerializer->endElementNS(XML_w, XML_numPicBullet);
10460 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */