| /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ |
| /* |
| * This file is part of the LibreOffice project. |
| * |
| * This Source Code Form is subject to the terms of the Mozilla Public |
| * License, v. 2.0. If a copy of the MPL was not distributed with this |
| * file, You can obtain one at http://mozilla.org/MPL/2.0/. |
| * |
| * This file incorporates work covered by the following license notice: |
| * |
| * Licensed to the Apache Software Foundation (ASF) under one or more |
| * contributor license agreements. See the NOTICE file distributed |
| * with this work for additional information regarding copyright |
| * ownership. The ASF licenses this file to you under the Apache |
| * License, Version 2.0 (the "License"); you may not use this file |
| * except in compliance with the License. You may obtain a copy of |
| * the License at http://www.apache.org/licenses/LICENSE-2.0 . |
| */ |
| |
| #include <memory> |
| #include "docxattributeoutput.hxx" |
| #include "docxhelper.hxx" |
| #include "docxsdrexport.hxx" |
| #include "docxexportfilter.hxx" |
| #include "docxfootnotes.hxx" |
| #include "writerwordglue.hxx" |
| #include "ww8par.hxx" |
| #include <fmtcntnt.hxx> |
| #include <fmtftn.hxx> |
| #include <fchrfmt.hxx> |
| #include <tgrditem.hxx> |
| #include <fmtruby.hxx> |
| #include <fmtfollowtextflow.hxx> |
| #include <fmtanchr.hxx> |
| #include <breakit.hxx> |
| #include <redline.hxx> |
| #include <unoframe.hxx> |
| #include <textboxhelper.hxx> |
| #include <rdfhelper.hxx> |
| #include "wrtww8.hxx" |
| |
| #include <comphelper/processfactory.hxx> |
| #include <comphelper/random.hxx> |
| #include <comphelper/string.hxx> |
| #include <comphelper/flagguard.hxx> |
| #include <comphelper/sequence.hxx> |
| #include <oox/token/namespaces.hxx> |
| #include <oox/token/tokens.hxx> |
| #include <oox/export/utils.hxx> |
| #include <oox/mathml/imexport.hxx> |
| #include <oox/drawingml/drawingmltypes.hxx> |
| #include <oox/token/relationship.hxx> |
| #include <oox/export/vmlexport.hxx> |
| #include <oox/ole/olehelper.hxx> |
| #include <oox/export/drawingml.hxx> |
| |
| #include <editeng/autokernitem.hxx> |
| #include <editeng/unoprnms.hxx> |
| #include <editeng/fontitem.hxx> |
| #include <editeng/tstpitem.hxx> |
| #include <editeng/spltitem.hxx> |
| #include <editeng/widwitem.hxx> |
| #include <editeng/shaditem.hxx> |
| #include <editeng/brushitem.hxx> |
| #include <editeng/postitem.hxx> |
| #include <editeng/wghtitem.hxx> |
| #include <editeng/kernitem.hxx> |
| #include <editeng/crossedoutitem.hxx> |
| #include <editeng/cmapitem.hxx> |
| #include <editeng/udlnitem.hxx> |
| #include <editeng/langitem.hxx> |
| #include <editeng/lspcitem.hxx> |
| #include <editeng/escapementitem.hxx> |
| #include <editeng/fhgtitem.hxx> |
| #include <editeng/colritem.hxx> |
| #include <editeng/hyphenzoneitem.hxx> |
| #include <editeng/ulspitem.hxx> |
| #include <editeng/contouritem.hxx> |
| #include <editeng/shdditem.hxx> |
| #include <editeng/emphasismarkitem.hxx> |
| #include <editeng/twolinesitem.hxx> |
| #include <editeng/charscaleitem.hxx> |
| #include <editeng/charrotateitem.hxx> |
| #include <editeng/charreliefitem.hxx> |
| #include <editeng/paravertalignitem.hxx> |
| #include <editeng/pgrditem.hxx> |
| #include <editeng/frmdiritem.hxx> |
| #include <editeng/blinkitem.hxx> |
| #include <editeng/charhiddenitem.hxx> |
| #include <editeng/editobj.hxx> |
| #include <editeng/keepitem.hxx> |
| #include <editeng/borderline.hxx> |
| #include <sax/tools/converter.hxx> |
| #include <svx/xdef.hxx> |
| #include <svx/xfillit0.hxx> |
| #include <svx/xflclit.hxx> |
| #include <svx/xflgrit.hxx> |
| #include <svx/svdouno.hxx> |
| #include <svx/unobrushitemhelper.hxx> |
| #include <svl/grabbagitem.hxx> |
| #include <tools/date.hxx> |
| #include <tools/datetime.hxx> |
| #include <tools/datetimeutils.hxx> |
| #include <svl/whiter.hxx> |
| #include <rtl/tencinfo.h> |
| #include <sal/log.hxx> |
| #include <sot/exchange.hxx> |
| |
| #include <docufld.hxx> |
| #include <authfld.hxx> |
| #include <flddropdown.hxx> |
| #include <fmtclds.hxx> |
| #include <fmtinfmt.hxx> |
| #include <fmtline.hxx> |
| #include <ftninfo.hxx> |
| #include <htmltbl.hxx> |
| #include <lineinfo.hxx> |
| #include <ndgrf.hxx> |
| #include <ndole.hxx> |
| #include <ndtxt.hxx> |
| #include <pagedesc.hxx> |
| #include <paratr.hxx> |
| #include <swmodule.hxx> |
| #include <swtable.hxx> |
| #include <txtftn.hxx> |
| #include <fmtautofmt.hxx> |
| #include <docsh.hxx> |
| #include <docary.hxx> |
| #include <fmtclbl.hxx> |
| #include <fmtftntx.hxx> |
| #include <IDocumentSettingAccess.hxx> |
| #include <IDocumentRedlineAccess.hxx> |
| #include <grfatr.hxx> |
| #include <frmatr.hxx> |
| #include <txtatr.hxx> |
| #include <frameformats.hxx> |
| #include <textcontentcontrol.hxx> |
| #include <formatflysplit.hxx> |
| |
| #include <o3tl/string_view.hxx> |
| #include <o3tl/unit_conversion.hxx> |
| #include <osl/file.hxx> |
| #include <utility> |
| #include <vcl/embeddedfontshelper.hxx> |
| |
| #include <com/sun/star/i18n/ScriptType.hpp> |
| #include <com/sun/star/i18n/XBreakIterator.hpp> |
| #include <com/sun/star/chart2/XChartDocument.hpp> |
| #include <com/sun/star/drawing/ShadingPattern.hpp> |
| #include <com/sun/star/text/GraphicCrop.hpp> |
| #include <com/sun/star/embed/EmbedStates.hpp> |
| #include <com/sun/star/embed/Aspects.hpp> |
| #include <com/sun/star/text/ControlCharacter.hpp> |
| |
| #include <algorithm> |
| #include <cstddef> |
| #include <stdarg.h> |
| #include <string_view> |
| |
| #include <toolkit/helper/vclunohelper.hxx> |
| #include <unicode/regex.h> |
| #include <frozen/bits/defines.h> |
| #include <frozen/bits/elsa_std.h> |
| #include <frozen/unordered_map.h> |
| #include <IDocumentDeviceAccess.hxx> |
| #include <sfx2/printer.hxx> |
| |
| using ::editeng::SvxBorderLine; |
| |
| using namespace oox; |
| using namespace docx; |
| using namespace sax_fastparser; |
| using namespace nsSwDocInfoSubType; |
| using namespace sw::util; |
| using namespace ::com::sun::star; |
| using namespace ::com::sun::star::drawing; |
| |
| namespace { |
| |
| class FFDataWriterHelper |
| { |
| ::sax_fastparser::FSHelperPtr m_pSerializer; |
| void writeCommonStart( const OUString& rName, |
| const OUString& rEntryMacro, |
| const OUString& rExitMacro, |
| const OUString& rHelp, |
| const OUString& rHint ) |
| { |
| m_pSerializer->startElementNS(XML_w, XML_ffData); |
| m_pSerializer->singleElementNS(XML_w, XML_name, FSNS(XML_w, XML_val), rName); |
| m_pSerializer->singleElementNS(XML_w, XML_enabled); |
| m_pSerializer->singleElementNS(XML_w, XML_calcOnExit, FSNS(XML_w, XML_val), "0"); |
| |
| if ( !rEntryMacro.isEmpty() ) |
| m_pSerializer->singleElementNS( XML_w, XML_entryMacro, |
| FSNS(XML_w, XML_val), rEntryMacro ); |
| |
| if ( !rExitMacro.isEmpty() ) |
| m_pSerializer->singleElementNS(XML_w, XML_exitMacro, FSNS(XML_w, XML_val), rExitMacro); |
| |
| if ( !rHelp.isEmpty() ) |
| m_pSerializer->singleElementNS( XML_w, XML_helpText, |
| FSNS(XML_w, XML_type), "text", |
| FSNS(XML_w, XML_val), rHelp ); |
| |
| if ( !rHint.isEmpty() ) |
| m_pSerializer->singleElementNS( XML_w, XML_statusText, |
| FSNS(XML_w, XML_type), "text", |
| FSNS(XML_w, XML_val), rHint ); |
| |
| } |
| void writeFinish() |
| { |
| m_pSerializer->endElementNS( XML_w, XML_ffData ); |
| } |
| public: |
| explicit FFDataWriterHelper( ::sax_fastparser::FSHelperPtr rSerializer ) : m_pSerializer(std::move( rSerializer )){} |
| void WriteFormCheckbox( const OUString& rName, |
| const OUString& rEntryMacro, |
| const OUString& rExitMacro, |
| const OUString& rHelp, |
| const OUString& rHint, |
| bool bChecked ) |
| { |
| writeCommonStart( rName, rEntryMacro, rExitMacro, rHelp, rHint ); |
| // Checkbox specific bits |
| m_pSerializer->startElementNS(XML_w, XML_checkBox); |
| // currently hardcoding autosize |
| // #TODO check if this defaulted |
| m_pSerializer->startElementNS(XML_w, XML_sizeAuto); |
| m_pSerializer->endElementNS( XML_w, XML_sizeAuto ); |
| if ( bChecked ) |
| m_pSerializer->singleElementNS(XML_w, XML_checked); |
| m_pSerializer->endElementNS( XML_w, XML_checkBox ); |
| writeFinish(); |
| } |
| |
| void WriteFormText( const OUString& rName, |
| const OUString& rEntryMacro, |
| const OUString& rExitMacro, |
| const OUString& rHelp, |
| const OUString& rHint, |
| const OUString& rType, |
| const OUString& rDefaultText, |
| sal_uInt16 nMaxLength, |
| const OUString& rFormat ) |
| { |
| writeCommonStart( rName, rEntryMacro, rExitMacro, rHelp, rHint ); |
| |
| m_pSerializer->startElementNS(XML_w, XML_textInput); |
| if ( !rType.isEmpty() ) |
| m_pSerializer->singleElementNS(XML_w, XML_type, FSNS(XML_w, XML_val), rType); |
| if ( !rDefaultText.isEmpty() ) |
| m_pSerializer->singleElementNS(XML_w, XML_default, FSNS(XML_w, XML_val), rDefaultText); |
| if ( nMaxLength ) |
| m_pSerializer->singleElementNS( XML_w, XML_maxLength, |
| FSNS(XML_w, XML_val), OString::number(nMaxLength) ); |
| if ( !rFormat.isEmpty() ) |
| m_pSerializer->singleElementNS(XML_w, XML_format, FSNS(XML_w, XML_val), rFormat); |
| m_pSerializer->endElementNS( XML_w, XML_textInput ); |
| |
| writeFinish(); |
| } |
| }; |
| |
| class FieldMarkParamsHelper |
| { |
| const sw::mark::IFieldmark& mrFieldmark; |
| public: |
| explicit FieldMarkParamsHelper( const sw::mark::IFieldmark& rFieldmark ) : mrFieldmark( rFieldmark ) {} |
| OUString const & getName() const { return mrFieldmark.GetName(); } |
| template < typename T > |
| bool extractParam( const OUString& rKey, T& rResult ) |
| { |
| bool bResult = false; |
| if ( mrFieldmark.GetParameters() ) |
| { |
| sw::mark::IFieldmark::parameter_map_t::const_iterator it = mrFieldmark.GetParameters()->find( rKey ); |
| if ( it != mrFieldmark.GetParameters()->end() ) |
| bResult = ( it->second >>= rResult ); |
| } |
| return bResult; |
| } |
| }; |
| |
| // [ISO/IEC29500-1:2016] 17.18.50 ST_LongHexNumber (Eight Digit Hexadecimal Value) |
| OUString NumberToHexBinary(sal_Int32 n) |
| { |
| OUStringBuffer aBuf; |
| sax::Converter::convertNumberToHexBinary(aBuf, n); |
| return aBuf.makeStringAndClear(); |
| } |
| |
| // Returns a new reference with the previous content of src; src is empty after this |
| auto detachFrom(rtl::Reference<sax_fastparser::FastAttributeList>& src) |
| { |
| return rtl::Reference(std::move(src)); |
| } |
| |
| void lclAddThemeValuesToCustomAttributes( |
| rtl::Reference<sax_fastparser::FastAttributeList>& pAttrList, model::ComplexColor const& rComplexColor, |
| sal_Int32 nThemeAttrId, sal_Int32 nThemeTintAttrId, sal_Int32 nThemeShadeAttrId) |
| { |
| static constexpr auto constThemeColorTypeTokenMap = frozen::make_unordered_map<model::ThemeColorType, const char*>({ |
| { model::ThemeColorType::Dark1, "dark1" }, |
| { model::ThemeColorType::Light1, "light1" }, |
| { model::ThemeColorType::Dark2, "dark2" }, |
| { model::ThemeColorType::Light2, "light2" }, |
| { model::ThemeColorType::Accent1, "accent1" }, |
| { model::ThemeColorType::Accent2, "accent2" }, |
| { model::ThemeColorType::Accent3, "accent3" }, |
| { model::ThemeColorType::Accent4, "accent4" }, |
| { model::ThemeColorType::Accent5, "accent5" }, |
| { model::ThemeColorType::Accent6, "accent6" }, |
| { model::ThemeColorType::Hyperlink, "hyperlink" }, |
| { model::ThemeColorType::FollowedHyperlink, "followedHyperlink" } |
| }); |
| |
| if (rComplexColor.isValidThemeType()) |
| { |
| const auto iter = constThemeColorTypeTokenMap.find(rComplexColor.getThemeColorType()); |
| assert(iter != constThemeColorTypeTokenMap.end()); |
| OString sSchemeType = iter->second; |
| if (rComplexColor.getThemeColorUsage() == model::ThemeColorUsage::Text) |
| { |
| if (rComplexColor.getThemeColorType() == model::ThemeColorType::Dark1) |
| sSchemeType = "text1"_ostr; |
| else if (rComplexColor.getThemeColorType() == model::ThemeColorType::Dark2) |
| sSchemeType = "text2"_ostr; |
| } |
| else if (rComplexColor.getThemeColorUsage() == model::ThemeColorUsage::Background) |
| { |
| if (rComplexColor.getThemeColorType() == model::ThemeColorType::Light1) |
| sSchemeType = "background1"_ostr; |
| else if (rComplexColor.getThemeColorType() == model::ThemeColorType::Light2) |
| sSchemeType = "background2"_ostr; |
| } |
| |
| DocxAttributeOutput::AddToAttrList(pAttrList, FSNS(XML_w, nThemeAttrId), sSchemeType); |
| |
| sal_Int16 nLumMod = 10'000; |
| sal_Int16 nLumOff = 0; |
| sal_Int16 nTint = 0; |
| sal_Int16 nShade = 0; |
| |
| for (auto const& rTransform : rComplexColor.getTransformations()) |
| { |
| if (rTransform.meType == model::TransformationType::LumMod) |
| nLumMod = rTransform.mnValue; |
| if (rTransform.meType == model::TransformationType::LumOff) |
| nLumOff = rTransform.mnValue; |
| if (rTransform.meType == model::TransformationType::Tint) |
| nTint = rTransform.mnValue; |
| if (rTransform.meType == model::TransformationType::Shade) |
| nShade = rTransform.mnValue; |
| } |
| if (nLumMod == 10'000 && nLumOff == 0) |
| { |
| if (nTint != 0) |
| { |
| // Convert from 0-100 into 0-255 |
| sal_Int16 nTint255 = std::round(255.0 - (double(nTint) / 10000.0) * 255.0); |
| DocxAttributeOutput::AddToAttrList(pAttrList, FSNS(XML_w, nThemeTintAttrId), OString::number(nTint255, 16)); |
| } |
| else if (nShade != 0) |
| { |
| // Convert from 0-100 into 0-255 |
| sal_Int16 nShade255 = std::round(255.0 - (double(nShade) / 10000.0) * 255.0); |
| DocxAttributeOutput::AddToAttrList(pAttrList, FSNS(XML_w, nThemeShadeAttrId), OString::number(nShade255, 16)); |
| } |
| } |
| else |
| { |
| double nPercentage = 0.0; |
| |
| if (nLumOff > 0) |
| nPercentage = double(nLumOff) / 100.0; |
| else |
| nPercentage = (-10'000 + double(nLumMod)) / 100.0; |
| |
| // Convert from 0-100 into 0-255 |
| sal_Int16 nTintShade255 = std::round(255.0 - (std::abs(nPercentage) / 100.0) * 255.0); |
| |
| if (nPercentage > 0) |
| DocxAttributeOutput::AddToAttrList(pAttrList, FSNS(XML_w, nThemeTintAttrId), OString::number(nTintShade255, 16)); |
| else if (nPercentage < 0) |
| DocxAttributeOutput::AddToAttrList(pAttrList, FSNS(XML_w, nThemeShadeAttrId), OString::number(nTintShade255, 16)); |
| } |
| } |
| } |
| |
| void lclAddThemeFillColorAttributes(rtl::Reference<sax_fastparser::FastAttributeList>& pAttrList, model::ComplexColor const& rComplexColor) |
| { |
| lclAddThemeValuesToCustomAttributes(pAttrList, rComplexColor, XML_themeFill, XML_themeFillTint, XML_themeFillShade); |
| } |
| |
| void lclAddThemeColorAttributes(rtl::Reference<sax_fastparser::FastAttributeList>& pAttrList, model::ComplexColor const& rComplexColor) |
| { |
| lclAddThemeValuesToCustomAttributes(pAttrList, rComplexColor, XML_themeColor, XML_themeTint, XML_themeShade); |
| } |
| |
| } // end anonymous namespace |
| |
| void DocxAttributeOutput::RTLAndCJKState( bool bIsRTL, sal_uInt16 /*nScript*/ ) |
| { |
| if (bIsRTL) |
| m_pSerializer->singleElementNS(XML_w, XML_rtl, FSNS(XML_w, XML_val), "true"); |
| } |
| |
| /// Are multiple paragraphs disallowed inside this type of SDT? |
| static bool lcl_isOnelinerSdt(std::u16string_view rName) |
| { |
| return rName == u"Title" || rName == u"Subtitle" || rName == u"Company"; |
| } |
| |
| // write a floating table directly to docx without the surrounding frame |
| void DocxAttributeOutput::WriteFloatingTable(ww8::Frame const* pParentFrame) |
| { |
| const SwFrameFormat& rFrameFormat = pParentFrame->GetFrameFormat(); |
| m_aFloatingTablesOfParagraph.insert(&rFrameFormat); |
| const SwNodeIndex* pNodeIndex = rFrameFormat.GetContent().GetContentIdx(); |
| |
| SwNodeOffset nStt = pNodeIndex ? pNodeIndex->GetIndex() + 1 : SwNodeOffset(0); |
| SwNodeOffset nEnd = pNodeIndex ? pNodeIndex->GetNode().EndOfSectionIndex() : SwNodeOffset(0); |
| |
| //Save data here and restore when out of scope |
| ExportDataSaveRestore aDataGuard(GetExport(), nStt, nEnd, pParentFrame); |
| |
| // Stash away info about the current table, so m_tableReference is clean. |
| DocxTableExportContext aTableExportContext(*this); |
| |
| // set a floatingTableFrame AND unset parent frame, |
| // otherwise exporter thinks we are still in a frame |
| m_rExport.SetFloatingTableFrame(pParentFrame); |
| m_rExport.m_pParentFrame = nullptr; |
| |
| GetExport().WriteText(); |
| |
| m_rExport.SetFloatingTableFrame(nullptr); |
| } |
| |
| static void checkAndWriteFloatingTables(DocxAttributeOutput& rDocxAttributeOutput) |
| { |
| const auto& rExport = rDocxAttributeOutput.GetExport(); |
| // iterate though all SpzFrameFormats and check whether they are anchored to the current text node |
| for( sal_uInt16 nCnt = rExport.m_rDoc.GetSpzFrameFormats()->size(); nCnt; ) |
| { |
| const SwFrameFormat* pFrameFormat = (*rExport.m_rDoc.GetSpzFrameFormats())[ --nCnt ]; |
| const SwFormatAnchor& rAnchor = pFrameFormat->GetAnchor(); |
| const SwNode* pAnchorNode = rAnchor.GetAnchorNode(); |
| |
| if (!pAnchorNode || ! rExport.m_pCurPam->GetPointNode().GetTextNode()) |
| continue; |
| |
| if (*pAnchorNode != *rExport.m_pCurPam->GetPointNode().GetTextNode()) |
| continue; |
| |
| const SwNodeIndex* pStartNode = pFrameFormat->GetContent().GetContentIdx(); |
| if (!pStartNode) |
| continue; |
| |
| SwNodeIndex aStartNode = *pStartNode; |
| |
| // go to the next node (actual content) |
| ++aStartNode; |
| |
| // this has to be a table |
| if (!aStartNode.GetNode().IsTableNode()) |
| continue; |
| |
| // go to the end of the table |
| SwNodeOffset aEndIndex = aStartNode.GetNode().EndOfSectionIndex(); |
| // go one deeper |
| aEndIndex++; |
| // this has to be the end of the content |
| if (aEndIndex != pFrameFormat->GetContent().GetContentIdx()->GetNode().EndOfSectionIndex()) |
| continue; |
| |
| // check for a grabBag and "TablePosition" attribute -> then we can export the table directly |
| SwTableNode* pTableNode = aStartNode.GetNode().GetTableNode(); |
| SwTable& rTable = pTableNode->GetTable(); |
| SwFrameFormat* pTableFormat = rTable.GetFrameFormat(); |
| const SfxGrabBagItem* pTableGrabBag = pTableFormat->GetAttrSet().GetItem<SfxGrabBagItem>(RES_FRMATR_GRABBAG); |
| const std::map<OUString, css::uno::Any> & rTableGrabBag = pTableGrabBag->GetGrabBag(); |
| // no grabbag? |
| if (rTableGrabBag.find(u"TablePosition"_ustr) == rTableGrabBag.end()) |
| { |
| if (pFrameFormat->GetFlySplit().GetValue()) |
| { |
| ww8::Frame aFrame(*pFrameFormat, *rAnchor.GetContentAnchor()); |
| rDocxAttributeOutput.WriteFloatingTable(&aFrame); |
| } |
| continue; |
| } |
| |
| // write table to docx |
| ww8::Frame aFrame(*pFrameFormat, *rAnchor.GetContentAnchor()); |
| rDocxAttributeOutput.WriteFloatingTable(&aFrame); |
| } |
| } |
| |
| sal_Int32 DocxAttributeOutput::StartParagraph(ww8::WW8TableNodeInfo::Pointer_t pTextNodeInfo, |
| bool bGenerateParaId) |
| { |
| // Paragraphs (in headers/footers/comments/frames etc) can start before another finishes. |
| // So a stack is needed to keep track of each paragraph's status separately. |
| // Complication: Word can't handle nested text boxes, so those need to be collected together. |
| if ( !m_aFramesOfParagraph.size() || !m_nTextFrameLevel ) |
| m_aFramesOfParagraph.push(std::vector<ww8::Frame>()); |
| |
| if ( m_nColBreakStatus == COLBRK_POSTPONE ) |
| m_nColBreakStatus = COLBRK_WRITE; |
| |
| // Output table/table row/table cell starts if needed |
| if ( pTextNodeInfo ) |
| { |
| // New cell/row? |
| if ( m_tableReference.m_nTableDepth > 0 && !m_tableReference.m_bTableCellOpen ) |
| { |
| ww8::WW8TableNodeInfoInner::Pointer_t pDeepInner( pTextNodeInfo->getInnerForDepth( m_tableReference.m_nTableDepth ) ); |
| if ( pDeepInner->getCell() == 0 ) |
| StartTableRow( pDeepInner ); |
| |
| const sal_uInt32 nCell = pDeepInner->getCell(); |
| const sal_uInt32 nRow = pDeepInner->getRow(); |
| |
| SyncNodelessCells(pDeepInner, nCell, nRow); |
| StartTableCell(pDeepInner, nCell, nRow); |
| } |
| |
| sal_uInt32 nRow = pTextNodeInfo->getRow(); |
| sal_uInt32 nCell = pTextNodeInfo->getCell(); |
| if (nCell == 0) |
| { |
| // Do we have to start the table? |
| // [If we are at the right depth already, it means that we |
| // continue the table cell] |
| sal_uInt32 nCurrentDepth = pTextNodeInfo->getDepth(); |
| |
| if ( nCurrentDepth > m_tableReference.m_nTableDepth ) |
| { |
| // Start all the tables that begin here |
| for ( sal_uInt32 nDepth = m_tableReference.m_nTableDepth + 1; nDepth <= nCurrentDepth; ++nDepth ) |
| { |
| ww8::WW8TableNodeInfoInner::Pointer_t pInner( pTextNodeInfo->getInnerForDepth( nDepth ) ); |
| |
| StartTable( pInner ); |
| StartTableRow( pInner ); |
| |
| StartTableCell(pInner, 0, nDepth == nCurrentDepth ? nRow : 0); |
| } |
| |
| m_tableReference.m_nTableDepth = nCurrentDepth; |
| } |
| } |
| } |
| |
| // look ahead for floating tables that were put into a frame during import |
| // floating tables in shapes are not supported: exclude this case |
| if (!m_rExport.SdrExporter().IsDMLAndVMLDrawingOpen()) |
| { |
| // Do this after opening table/row/cell, so floating tables anchored at cell start go inside |
| // the cell, not outside. |
| checkAndWriteFloatingTables(*this); |
| } |
| |
| // Look up the "sdt end before this paragraph" property early, when it |
| // would normally arrive, it would be too late (would be after the |
| // paragraph start has been written). |
| bool bEndParaSdt = false; |
| if (m_aParagraphSdt.m_bStartedSdt) |
| { |
| SwTextNode* pTextNode = m_rExport.m_pCurPam->GetPointNode().GetTextNode(); |
| if (pTextNode && pTextNode->GetpSwAttrSet()) |
| { |
| const SfxItemSet* pSet = pTextNode->GetpSwAttrSet(); |
| if (const SfxPoolItem* pItem = pSet->GetItem(RES_PARATR_GRABBAG)) |
| { |
| const SfxGrabBagItem& rParaGrabBag = static_cast<const SfxGrabBagItem&>(*pItem); |
| const std::map<OUString, css::uno::Any>& rMap = rParaGrabBag.GetGrabBag(); |
| bEndParaSdt = m_aParagraphSdt.m_bStartedSdt && rMap.contains(u"ParaSdtEndBefore"_ustr); |
| } |
| } |
| } |
| // TODO also avoid multiline paragraphs in those SDT types for shape text |
| bool bOneliner = m_aParagraphSdt.m_bStartedSdt && !m_rExport.SdrExporter().IsDMLAndVMLDrawingOpen() && lcl_isOnelinerSdt(m_aStartedParagraphSdtPrAlias); |
| if (bEndParaSdt || (m_aParagraphSdt.m_bStartedSdt && m_bHadSectPr) || bOneliner) |
| { |
| // This is the common case: "close sdt before the current paragraph" was requested by the next paragraph. |
| m_aParagraphSdt.EndSdtBlock(m_pSerializer); |
| m_aStartedParagraphSdtPrAlias.clear(); |
| } |
| m_bHadSectPr = false; |
| |
| // this mark is used to be able to enclose the paragraph inside a sdr tag. |
| // We will only know if we have to do that later. |
| m_pSerializer->mark(Tag_StartParagraph_1); |
| |
| std::optional<OUString> aParaId; |
| sal_Int32 nParaId = 0; |
| if (bGenerateParaId) |
| { |
| nParaId = m_nNextParaId++; |
| aParaId = NumberToHexBinary(nParaId); |
| } |
| m_pSerializer->startElementNS(XML_w, XML_p, FSNS(XML_w14, XML_paraId), aParaId); |
| |
| // postpone the output of the run (we get it before the paragraph |
| // properties, but must write it after them) |
| m_pSerializer->mark(Tag_StartParagraph_2); |
| |
| // no section break in this paragraph yet; can be set in SectionBreak() |
| m_pSectionInfo.reset(); |
| |
| m_bParagraphOpened = true; |
| m_bIsFirstParagraph = false; |
| m_nHyperLinkCount.push_back(0); |
| |
| return nParaId; |
| } |
| |
| OString DocxAttributeOutput::convertToOOXMLVertOrient(sal_Int16 nOrient) |
| { |
| switch( nOrient ) |
| { |
| case text::VertOrientation::CENTER: |
| case text::VertOrientation::LINE_CENTER: |
| return "center"_ostr; |
| case text::VertOrientation::BOTTOM: |
| return "bottom"_ostr; |
| case text::VertOrientation::LINE_BOTTOM: |
| return "outside"_ostr; |
| case text::VertOrientation::TOP: |
| return "top"_ostr; |
| case text::VertOrientation::LINE_TOP: |
| return "inside"_ostr; |
| default: |
| return OString(); |
| } |
| } |
| |
| OString DocxAttributeOutput::convertToOOXMLHoriOrient(sal_Int16 nOrient, bool bIsPosToggle) |
| { |
| switch( nOrient ) |
| { |
| case text::HoriOrientation::LEFT: |
| return bIsPosToggle ? "inside" : "left"; |
| case text::HoriOrientation::INSIDE: |
| return "inside"_ostr; |
| case text::HoriOrientation::RIGHT: |
| return bIsPosToggle ? "outside" : "right"; |
| case text::HoriOrientation::OUTSIDE: |
| return "outside"_ostr; |
| case text::HoriOrientation::CENTER: |
| case text::HoriOrientation::FULL: |
| return "center"_ostr; |
| default: |
| return OString(); |
| } |
| } |
| |
| OString DocxAttributeOutput::convertToOOXMLVertOrientRel(sal_Int16 nOrientRel) |
| { |
| switch (nOrientRel) |
| { |
| case text::RelOrientation::PAGE_PRINT_AREA: |
| return "margin"_ostr; |
| case text::RelOrientation::PAGE_FRAME: |
| return "page"_ostr; |
| case text::RelOrientation::FRAME: |
| case text::RelOrientation::TEXT_LINE: |
| default: |
| return "text"_ostr; |
| } |
| } |
| |
| OString DocxAttributeOutput::convertToOOXMLHoriOrientRel(sal_Int16 nOrientRel) |
| { |
| switch (nOrientRel) |
| { |
| case text::RelOrientation::PAGE_PRINT_AREA: |
| return "margin"_ostr; |
| case text::RelOrientation::PAGE_FRAME: |
| return "page"_ostr; |
| case text::RelOrientation::CHAR: |
| case text::RelOrientation::PAGE_RIGHT: |
| case text::RelOrientation::FRAME: |
| default: |
| return "text"_ostr; |
| } |
| } |
| |
| void FramePrHelper::SetFrame(ww8::Frame* pFrame, sal_Int32 nTableDepth) |
| { |
| assert(!pFrame || !m_pFrame); |
| m_pFrame = pFrame; |
| m_nTableDepth = nTableDepth; |
| if (m_pFrame) |
| { |
| m_bUseFrameBorders = true; |
| m_bUseFrameBackground = true; |
| m_bUseFrameTextDirection = true; |
| } |
| } |
| |
| bool FramePrHelper::UseFrameBorders(sal_Int32 nTableDepth) |
| { |
| if (!m_pFrame || m_nTableDepth < nTableDepth) |
| return false; |
| |
| return m_bUseFrameBorders; |
| } |
| |
| bool FramePrHelper::UseFrameBackground() |
| { |
| if (!m_pFrame) |
| return false; |
| |
| return m_bUseFrameBackground; |
| } |
| |
| bool FramePrHelper::UseFrameTextDirection(sal_Int32 nTableDepth) |
| { |
| if (!m_pFrame || m_nTableDepth < nTableDepth) |
| return false; |
| |
| return m_bUseFrameTextDirection; |
| } |
| |
| void SdtBlockHelper::DeleteAndResetTheLists() |
| { |
| if (m_pTokenChildren.is() ) |
| m_pTokenChildren.clear(); |
| if (m_pDataBindingAttrs.is() ) |
| m_pDataBindingAttrs.clear(); |
| if (m_pTextAttrs.is()) |
| m_pTextAttrs.clear(); |
| if (!m_aAlias.isEmpty()) |
| m_aAlias.clear(); |
| if (!m_aTag.isEmpty()) |
| m_aTag.clear(); |
| if (!m_aLock.isEmpty()) |
| m_aLock.clear(); |
| if (!m_aPlaceHolderDocPart.isEmpty()) |
| m_aPlaceHolderDocPart.clear(); |
| if (!m_aColor.isEmpty()) |
| m_aColor.clear(); |
| if (!m_aAppearance.isEmpty()) |
| m_aAppearance.clear(); |
| m_bShowingPlaceHolder = false; |
| m_nId = 0; |
| m_nTabIndex = 0; |
| } |
| |
| void SdtBlockHelper::WriteSdtBlock(const ::sax_fastparser::FSHelperPtr& pSerializer, bool bRunTextIsOn, bool bParagraphHasDrawing) |
| { |
| if (m_nSdtPrToken <= 0 && !m_pDataBindingAttrs.is() && !m_nId) |
| return; |
| |
| // sdt start mark |
| pSerializer->mark(DocxAttributeOutput::Tag_WriteSdtBlock); |
| |
| pSerializer->startElementNS(XML_w, XML_sdt); |
| |
| // output sdt properties |
| pSerializer->startElementNS(XML_w, XML_sdtPr); |
| |
| if (m_nSdtPrToken > 0 && m_pTokenChildren.is()) |
| { |
| if (!m_pTokenAttributes.is()) |
| pSerializer->startElement(m_nSdtPrToken); |
| else |
| { |
| pSerializer->startElement(m_nSdtPrToken, detachFrom(m_pTokenAttributes)); |
| } |
| |
| 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)) { |
| const uno::Sequence<xml::FastAttribute> aChildren = m_pTokenChildren->getFastAttributes(); |
| for (const auto& rChild : aChildren) |
| pSerializer->singleElement(rChild.Token, FSNS(XML_w, XML_val), rChild.Value); |
| } |
| |
| pSerializer->endElement(m_nSdtPrToken); |
| } |
| else if ((m_nSdtPrToken > 0) && m_nSdtPrToken != FSNS(XML_w, XML_id) && !(bRunTextIsOn && bParagraphHasDrawing)) |
| { |
| if (!m_pTokenAttributes.is()) |
| pSerializer->singleElement(m_nSdtPrToken); |
| else |
| { |
| pSerializer->singleElement(m_nSdtPrToken, detachFrom(m_pTokenAttributes)); |
| } |
| } |
| |
| WriteExtraParams(pSerializer); |
| |
| pSerializer->endElementNS(XML_w, XML_sdtPr); |
| |
| // sdt contents start tag |
| pSerializer->startElementNS(XML_w, XML_sdtContent); |
| |
| // prepend the tags since the sdt start mark before the paragraph |
| pSerializer->mergeTopMarks(DocxAttributeOutput::Tag_WriteSdtBlock, sax_fastparser::MergeMarks::PREPEND); |
| |
| // write the ending tags after the paragraph |
| m_bStartedSdt = true; |
| |
| // clear sdt status |
| m_nSdtPrToken = 0; |
| DeleteAndResetTheLists(); |
| } |
| |
| void SdtBlockHelper::WriteExtraParams(const ::sax_fastparser::FSHelperPtr& pSerializer) |
| { |
| if (m_nId) |
| { |
| pSerializer->singleElementNS(XML_w, XML_id, FSNS(XML_w, XML_val), OString::number(m_nId)); |
| } |
| |
| if (m_pDataBindingAttrs.is()) |
| { |
| pSerializer->singleElementNS(XML_w, XML_dataBinding, detachFrom(m_pDataBindingAttrs)); |
| } |
| |
| if (m_pTextAttrs.is()) |
| { |
| pSerializer->singleElementNS(XML_w, XML_text, detachFrom(m_pTextAttrs)); |
| } |
| |
| if (!m_aPlaceHolderDocPart.isEmpty()) |
| { |
| pSerializer->startElementNS(XML_w, XML_placeholder); |
| pSerializer->singleElementNS(XML_w, XML_docPart, FSNS(XML_w, XML_val), m_aPlaceHolderDocPart); |
| pSerializer->endElementNS(XML_w, XML_placeholder); |
| } |
| |
| if (m_bShowingPlaceHolder) |
| pSerializer->singleElementNS(XML_w, XML_showingPlcHdr); |
| |
| if (!m_aColor.isEmpty()) |
| { |
| pSerializer->singleElementNS(XML_w15, XML_color, FSNS(XML_w, XML_val), m_aColor); |
| } |
| |
| if (!m_aAppearance.isEmpty()) |
| { |
| pSerializer->singleElementNS(XML_w15, XML_appearance, FSNS(XML_w15, XML_val), m_aAppearance); |
| } |
| |
| if (!m_aAlias.isEmpty()) |
| pSerializer->singleElementNS(XML_w, XML_alias, FSNS(XML_w, XML_val), m_aAlias); |
| |
| if (!m_aTag.isEmpty()) |
| pSerializer->singleElementNS(XML_w, XML_tag, FSNS(XML_w, XML_val), m_aTag); |
| |
| if (m_nTabIndex) |
| pSerializer->singleElementNS(XML_w, XML_tabIndex, FSNS(XML_w, XML_val), |
| OString::number(m_nTabIndex)); |
| |
| if (!m_aLock.isEmpty()) |
| pSerializer->singleElementNS(XML_w, XML_lock, FSNS(XML_w, XML_val), m_aLock); |
| } |
| |
| void SdtBlockHelper::EndSdtBlock(const ::sax_fastparser::FSHelperPtr& pSerializer) |
| { |
| pSerializer->endElementNS(XML_w, XML_sdtContent); |
| pSerializer->endElementNS(XML_w, XML_sdt); |
| m_bStartedSdt = false; |
| } |
| |
| void SdtBlockHelper::GetSdtParamsFromGrabBag(const uno::Sequence<beans::PropertyValue>& aGrabBagSdt) |
| { |
| for (const beans::PropertyValue& aPropertyValue : aGrabBagSdt) |
| { |
| if (aPropertyValue.Name == "ooxml:CT_SdtPr_checkbox") |
| { |
| m_nSdtPrToken = FSNS(XML_w14, XML_checkbox); |
| uno::Sequence<beans::PropertyValue> aGrabBag; |
| aPropertyValue.Value >>= aGrabBag; |
| for (const auto& rProp : aGrabBag) |
| { |
| if (rProp.Name == "ooxml:CT_SdtCheckbox_checked") |
| DocxAttributeOutput::AddToAttrList(m_pTokenChildren, |
| FSNS(XML_w14, XML_checked), rProp.Value.get<OUString>()); |
| else if (rProp.Name == "ooxml:CT_SdtCheckbox_checkedState") |
| DocxAttributeOutput::AddToAttrList(m_pTokenChildren, |
| FSNS(XML_w14, XML_checkedState), rProp.Value.get<OUString>()); |
| else if (rProp.Name == "ooxml:CT_SdtCheckbox_uncheckedState") |
| DocxAttributeOutput::AddToAttrList(m_pTokenChildren, |
| FSNS(XML_w14, XML_uncheckedState), rProp.Value.get<OUString>()); |
| } |
| } |
| else if (aPropertyValue.Name == "ooxml:CT_SdtPr_dataBinding" && !m_pDataBindingAttrs.is()) |
| { |
| uno::Sequence<beans::PropertyValue> aGrabBag; |
| aPropertyValue.Value >>= aGrabBag; |
| for (const auto& rProp : aGrabBag) |
| { |
| if (rProp.Name == "ooxml:CT_DataBinding_prefixMappings") |
| DocxAttributeOutput::AddToAttrList( m_pDataBindingAttrs, |
| FSNS( XML_w, XML_prefixMappings ), rProp.Value.get<OUString>()); |
| else if (rProp.Name == "ooxml:CT_DataBinding_xpath") |
| DocxAttributeOutput::AddToAttrList( m_pDataBindingAttrs, |
| FSNS( XML_w, XML_xpath ), rProp.Value.get<OUString>()); |
| else if (rProp.Name == "ooxml:CT_DataBinding_storeItemID") |
| DocxAttributeOutput::AddToAttrList( m_pDataBindingAttrs, |
| FSNS( XML_w, XML_storeItemID ), rProp.Value.get<OUString>()); |
| } |
| } |
| else if (aPropertyValue.Name == "ooxml:CT_SdtPr_text") |
| { |
| uno::Sequence<beans::PropertyValue> aGrabBag; |
| aPropertyValue.Value >>= aGrabBag; |
| if (aGrabBag.hasElements()) |
| { |
| for (const auto& rProp : aGrabBag) |
| { |
| if (rProp.Name == "ooxml:CT_SdtText_multiLine") |
| DocxAttributeOutput::AddToAttrList(m_pTextAttrs, |
| FSNS(XML_w, XML_multiLine), rProp.Value.get<OUString>()); |
| } |
| } |
| else |
| { |
| // We still have w:text, but no attrs |
| m_nSdtPrToken = FSNS(XML_w, XML_text); |
| } |
| } |
| else if (aPropertyValue.Name == "ooxml:CT_SdtPlaceholder_docPart") |
| { |
| uno::Sequence<beans::PropertyValue> aGrabBag; |
| aPropertyValue.Value >>= aGrabBag; |
| for (const auto& rProp : aGrabBag) |
| { |
| if (rProp.Name == "ooxml:CT_SdtPlaceholder_docPart_val") |
| m_aPlaceHolderDocPart = rProp.Value.get<OUString>(); |
| } |
| } |
| else if (aPropertyValue.Name == "ooxml:CT_SdtPr_color") |
| { |
| uno::Sequence<beans::PropertyValue> aGrabBag; |
| aPropertyValue.Value >>= aGrabBag; |
| for (const auto& rProp : aGrabBag) |
| { |
| if (rProp.Name == "ooxml:CT_SdtColor_val") |
| m_aColor = rProp.Value.get<OUString>(); |
| } |
| } |
| else if (aPropertyValue.Name == "ooxml:CT_SdtPr_appearance") |
| { |
| if (!(aPropertyValue.Value >>= m_aAppearance)) |
| SAL_WARN("sw.ww8", "DocxAttributeOutput::GrabBag: unexpected sdt appearance value"); |
| } |
| else if (aPropertyValue.Name == "ooxml:CT_SdtPr_showingPlcHdr") |
| { |
| if (!(aPropertyValue.Value >>= m_bShowingPlaceHolder)) |
| SAL_WARN("sw.ww8", "DocxAttributeOutput::GrabBag: unexpected sdt ShowingPlcHdr"); |
| } |
| else if (aPropertyValue.Name == "ooxml:CT_SdtPr_alias" && m_aAlias.isEmpty()) |
| { |
| if (!(aPropertyValue.Value >>= m_aAlias)) |
| SAL_WARN("sw.ww8", "DocxAttributeOutput::GrabBag: unexpected sdt alias value"); |
| } |
| else if (aPropertyValue.Name == "ooxml:CT_SdtPr_tag" && m_aTag.isEmpty()) |
| { |
| if (!(aPropertyValue.Value >>= m_aTag)) |
| SAL_WARN("sw.ww8", "DocxAttributeOutput::GrabBag: unexpected sdt tag value"); |
| } |
| else if (aPropertyValue.Name == "ooxml:CT_SdtPr_id") |
| { |
| if (!(aPropertyValue.Value >>= m_nId)) |
| SAL_WARN("sw.ww8", "DocxAttributeOutput::GrabBag: unexpected sdt id value"); |
| } |
| else if (aPropertyValue.Name == "ooxml:CT_SdtPr_tabIndex" && !m_nTabIndex) |
| { |
| if (!(aPropertyValue.Value >>= m_nTabIndex)) |
| SAL_WARN("sw.ww8", "DocxAttributeOutput::GrabBag: unexpected sdt tabIndex value"); |
| } |
| else if (aPropertyValue.Name == "ooxml:CT_SdtPr_lock" && m_aLock.isEmpty()) |
| { |
| if (!(aPropertyValue.Value >>= m_aLock)) |
| SAL_WARN("sw.ww8", "DocxAttributeOutput::GrabBag: unexpected sdt lock value"); |
| } |
| else if (aPropertyValue.Name == "ooxml:CT_SdtPr_citation") |
| m_nSdtPrToken = FSNS(XML_w, XML_citation); |
| else if (aPropertyValue.Name == "ooxml:CT_SdtPr_docPartObj" || |
| aPropertyValue.Name == "ooxml:CT_SdtPr_docPartList") |
| { |
| if (aPropertyValue.Name == "ooxml:CT_SdtPr_docPartObj") |
| m_nSdtPrToken = FSNS(XML_w, XML_docPartObj); |
| else if (aPropertyValue.Name == "ooxml:CT_SdtPr_docPartList") |
| m_nSdtPrToken = FSNS(XML_w, XML_docPartList); |
| |
| uno::Sequence<beans::PropertyValue> aGrabBag; |
| aPropertyValue.Value >>= aGrabBag; |
| for (const auto& rProp : aGrabBag) |
| { |
| if (rProp.Name == "ooxml:CT_SdtDocPart_docPartGallery") |
| DocxAttributeOutput::AddToAttrList(m_pTokenChildren, |
| FSNS(XML_w, XML_docPartGallery), rProp.Value.get<OUString>()); |
| else if (rProp.Name == "ooxml:CT_SdtDocPart_docPartCategory") |
| DocxAttributeOutput::AddToAttrList(m_pTokenChildren, |
| FSNS(XML_w, XML_docPartCategory), rProp.Value.get<OUString>()); |
| else if (rProp.Name == "ooxml:CT_SdtDocPart_docPartUnique") |
| { |
| OUString sValue = rProp.Value.get<OUString>(); |
| if (sValue.isEmpty()) |
| sValue = "true"; |
| DocxAttributeOutput::AddToAttrList(m_pTokenChildren, FSNS(XML_w, XML_docPartUnique), |
| sValue); |
| } |
| } |
| } |
| else if (aPropertyValue.Name == "ooxml:CT_SdtPr_equation") |
| m_nSdtPrToken = FSNS(XML_w, XML_equation); |
| else if (aPropertyValue.Name == "ooxml:CT_SdtPr_picture") |
| m_nSdtPrToken = FSNS(XML_w, XML_picture); |
| else if (aPropertyValue.Name == "ooxml:CT_SdtPr_group") |
| m_nSdtPrToken = FSNS(XML_w, XML_group); |
| else |
| SAL_WARN("sw.ww8", "GetSdtParamsFromGrabBag unhandled SdtPr grab bag property " << aPropertyValue.Name); |
| } |
| } |
| |
| void DocxAttributeOutput::PopulateFrameProperties(const SwFrameFormat* pFrameFormat, const Size& rSize) |
| { |
| rtl::Reference<sax_fastparser::FastAttributeList> attrList = FastSerializerHelper::createAttrList(); |
| |
| const SwFormatHoriOrient& rHoriOrient = pFrameFormat->GetHoriOrient(); |
| const SwFormatVertOrient& rVertOrient = pFrameFormat->GetVertOrient(); |
| awt::Point aPos(rHoriOrient.GetPos(), rVertOrient.GetPos()); |
| |
| // A few assumptions need to be made here, because framePr is a confused mixture |
| // of (multiple) paragraph's border properties being transferred to/from a frame. |
| // The frame size describes the size BEFORE the PARAGRAPH border spacing is applied. |
| // However, we can't actually look at all the paragraphs' borders because they might be |
| // different, and all MUST specify the same frame width in order to belong to the same frame. |
| // In order for them all to be consistent, the only choice is to use the frame's border spacing. |
| // During import, the frame was assigned border spacing based on the contained paragraphs. |
| // So now at export time we have to assume that none of this has been changed by the user. |
| |
| // 620 (31pt) is the maximum paragraph border spacing allowed in MS Formats, |
| // so if the value is greater than that, avoid adjusting the size - the user has interfered. |
| const sal_uInt32 nLeftBorderSpacing = pFrameFormat->GetBox().GetDistance(SvxBoxItemLine::LEFT); |
| const sal_uInt32 nRighttBorderSpacing = pFrameFormat->GetBox().GetDistance(SvxBoxItemLine::RIGHT); |
| sal_uInt32 nAdjustedWidth = rSize.Width(); |
| if (nLeftBorderSpacing < 621 && nRighttBorderSpacing < 621 |
| && nAdjustedWidth > nLeftBorderSpacing + nRighttBorderSpacing) |
| { |
| nAdjustedWidth -= nLeftBorderSpacing + nRighttBorderSpacing; |
| } |
| attrList->add( FSNS( XML_w, XML_w), OString::number(nAdjustedWidth)); |
| attrList->add( FSNS( XML_w, XML_h), OString::number(rSize.Height())); |
| |
| const OString relativeFromH = convertToOOXMLHoriOrientRel(rHoriOrient.GetRelationOrient()); |
| const OString relativeFromV = convertToOOXMLVertOrientRel(rVertOrient.GetRelationOrient()); |
| OString aXAlign = convertToOOXMLHoriOrient(rHoriOrient.GetHoriOrient(), /*bIsPosToggle=*/false); |
| OString aYAlign = convertToOOXMLVertOrient(rVertOrient.GetVertOrient()); |
| if (!aXAlign.isEmpty()) |
| attrList->add(FSNS(XML_w, XML_xAlign), aXAlign); |
| else if (aPos.X) |
| attrList->add( FSNS( XML_w, XML_x), OString::number(aPos.X)); |
| if (!aYAlign.isEmpty() && relativeFromV != "text") |
| attrList->add(FSNS(XML_w, XML_yAlign), aYAlign); |
| else if (aPos.Y) |
| attrList->add( FSNS( XML_w, XML_y), OString::number(aPos.Y)); |
| |
| sal_Int16 nLeft = pFrameFormat->GetLRSpace().GetLeft(); |
| sal_Int16 nRight = pFrameFormat->GetLRSpace().GetRight(); |
| sal_Int16 nUpper = pFrameFormat->GetULSpace().GetUpper(); |
| sal_Int16 nLower = pFrameFormat->GetULSpace().GetLower(); |
| |
| // To emulate, on import left was ignored (set to zero) if aligned to left, |
| // so just double up the right spacing in order to prevent cutting in half each round-trip. |
| if (rHoriOrient.GetHoriOrient() == text::HoriOrientation::LEFT) |
| nLeft = nRight; |
| else if (rHoriOrient.GetHoriOrient() == text::HoriOrientation::RIGHT) |
| nRight = nLeft; |
| |
| attrList->add(FSNS(XML_w, XML_hSpace), OString::number((nLeft + nRight) / 2)); |
| attrList->add(FSNS(XML_w, XML_vSpace), OString::number((nUpper + nLower) / 2)); |
| |
| switch (pFrameFormat->GetSurround().GetValue()) |
| { |
| case css::text::WrapTextMode_NONE: |
| attrList->add( FSNS( XML_w, XML_wrap), "notBeside"); |
| break; |
| case css::text::WrapTextMode_DYNAMIC: |
| attrList->add(FSNS(XML_w, XML_wrap), "auto"); |
| break; |
| case css::text::WrapTextMode_PARALLEL: |
| default: |
| attrList->add(FSNS(XML_w, XML_wrap), "around"); |
| break; |
| } |
| attrList->add( FSNS( XML_w, XML_vAnchor), relativeFromV ); |
| attrList->add( FSNS( XML_w, XML_hAnchor), relativeFromH ); |
| attrList->add( FSNS( XML_w, XML_hRule), "exact"); |
| |
| m_pSerializer->singleElementNS( XML_w, XML_framePr, attrList ); |
| } |
| |
| bool DocxAttributeOutput::TextBoxIsFramePr(const SwFrameFormat& rFrameFormat) |
| { |
| SdrObject* pSdrObj = const_cast<SdrObject*>(rFrameFormat.FindRealSdrObject()); |
| if (!pSdrObj) |
| return false; |
| |
| uno::Reference<beans::XPropertySet> xPropertySet(pSdrObj->getUnoShape(), uno::UNO_QUERY); |
| if (!xPropertySet.is()) |
| return false; |
| |
| uno::Reference<beans::XPropertySetInfo> xPropSetInfo(xPropertySet->getPropertySetInfo()); |
| if (!xPropSetInfo.is() || !xPropSetInfo->hasPropertyByName(u"FrameInteropGrabBag"_ustr)) |
| return false; |
| |
| bool bRet = false; |
| uno::Sequence<beans::PropertyValue> propList; |
| xPropertySet->getPropertyValue(u"FrameInteropGrabBag"_ustr) >>= propList; |
| auto pProp = std::find_if(std::cbegin(propList), std::cend(propList), |
| [](const beans::PropertyValue& rProp) { return rProp.Name == "ParaFrameProperties"; }); |
| if (pProp != std::cend(propList)) |
| pProp->Value >>= bRet; |
| |
| return bRet; |
| } |
| |
| void DocxAttributeOutput::EndParagraph( ww8::WW8TableNodeInfoInner::Pointer_t pTextNodeInfoInner ) |
| { |
| // write the paragraph properties + the run, already in the correct order |
| m_pSerializer->mergeTopMarks(Tag_StartParagraph_2); |
| std::vector< std::shared_ptr <ww8::Frame> > aFramePrTextbox; |
| // Write the anchored frame if any |
| // Word can't handle nested text boxes, so write them on the same level. |
| ++m_nTextFrameLevel; |
| if( m_nTextFrameLevel == 1 && !m_rExport.SdrExporter().IsDMLAndVMLDrawingOpen() ) |
| { |
| comphelper::FlagRestorationGuard aStartedParaSdtGuard(m_aParagraphSdt.m_bStartedSdt, false); |
| |
| assert(!m_oPostponedCustomShape); |
| m_oPostponedCustomShape.emplace(); |
| |
| // The for loop can change the size of m_aFramesOfParagraph, so the max size cannot be set in stone before the loop. |
| size_t nFrames = m_aFramesOfParagraph.size() ? m_aFramesOfParagraph.top().size() : 0; |
| for (size_t nIndex = 0; nIndex < nFrames; ++nIndex) |
| { |
| m_bParagraphFrameOpen = true; |
| ww8::Frame aFrame = m_aFramesOfParagraph.top()[nIndex]; |
| const SwFrameFormat& rFrameFormat = aFrame.GetFrameFormat(); |
| |
| if (!m_bWritingHeaderFooter && TextBoxIsFramePr(rFrameFormat)) |
| { |
| std::shared_ptr<ww8::Frame> pFramePr = std::make_shared<ww8::Frame>(aFrame); |
| aFramePrTextbox.push_back(pFramePr); |
| } |
| else |
| { |
| if (m_aRunSdt.m_bStartedSdt) |
| { |
| // Run-level SDT still open? Close it before AlternateContent. |
| m_aRunSdt.EndSdtBlock(m_pSerializer); |
| } |
| m_pSerializer->startElementNS(XML_w, XML_r); |
| m_pSerializer->startElementNS(XML_mc, XML_AlternateContent); |
| m_pSerializer->startElementNS(XML_mc, XML_Choice, XML_Requires, "wps"); |
| /** |
| This is to avoid AlternateContent within another AlternateContent. |
| So when Choice is Open, only write the DML Drawing instead of both DML |
| and VML Drawing in another AlternateContent. |
| **/ |
| SetAlternateContentChoiceOpen( true ); |
| /** Save the table info's before writing the shape |
| as there might be a new table that might get |
| spawned from within the VML & DML block and alter |
| the contents. |
| */ |
| ww8::WW8TableInfo::Pointer_t xOldTableInfo = m_rExport.m_pTableInfo; |
| //Reset the table infos after saving. |
| m_rExport.m_pTableInfo = std::make_shared<ww8::WW8TableInfo>(); |
| |
| /** FDO#71834 : |
| Save the table reference attributes before calling WriteDMLTextFrame, |
| otherwise the StartParagraph function will use the previous existing |
| table reference attributes since the variable is being shared. |
| */ |
| { |
| DocxTableExportContext aDMLTableExportContext(*this); |
| m_rExport.SdrExporter().writeDMLTextFrame(&aFrame, m_anchorId++); |
| } |
| m_pSerializer->endElementNS(XML_mc, XML_Choice); |
| SetAlternateContentChoiceOpen( false ); |
| |
| // Reset table infos, otherwise the depth of the cells will be incorrect, |
| // in case the text frame had table(s) and we try to export the |
| // same table second time. |
| m_rExport.m_pTableInfo = std::make_shared<ww8::WW8TableInfo>(); |
| //reset the tableReference. |
| |
| m_pSerializer->startElementNS(XML_mc, XML_Fallback); |
| { |
| DocxTableExportContext aVMLTableExportContext(*this); |
| m_rExport.SdrExporter().writeVMLTextFrame(&aFrame); |
| } |
| m_rExport.m_pTableInfo = std::move(xOldTableInfo); |
| |
| m_pSerializer->endElementNS(XML_mc, XML_Fallback); |
| m_pSerializer->endElementNS(XML_mc, XML_AlternateContent); |
| m_pSerializer->endElementNS( XML_w, XML_r ); |
| m_bParagraphFrameOpen = false; |
| } |
| |
| nFrames = m_aFramesOfParagraph.size() ? m_aFramesOfParagraph.top().size() : 0; |
| } |
| if (!m_oPostponedCustomShape->empty()) |
| { |
| m_pSerializer->startElementNS(XML_w, XML_r); |
| WritePostponedCustomShape(); |
| m_pSerializer->endElementNS( XML_w, XML_r ); |
| } |
| m_oPostponedCustomShape.reset(); |
| |
| if ( m_aFramesOfParagraph.size() ) |
| m_aFramesOfParagraph.top().clear(); |
| |
| if (!pTextNodeInfoInner) |
| { |
| // Ending a non-table paragraph, clear floating tables before paragraph. |
| m_aFloatingTablesOfParagraph.clear(); |
| } |
| } |
| |
| --m_nTextFrameLevel; |
| if ( m_aFramesOfParagraph.size() && !m_nTextFrameLevel ) |
| m_aFramesOfParagraph.pop(); |
| |
| /* If m_nHyperLinkCount > 0 that means hyperlink tag is not yet closed. |
| * This is due to nested hyperlink tags. So close it before end of paragraph. |
| */ |
| if(m_nHyperLinkCount.back() > 0) |
| { |
| for(sal_Int32 nHyperLinkToClose = 0; nHyperLinkToClose < m_nHyperLinkCount.back(); ++nHyperLinkToClose) |
| m_pSerializer->endElementNS( XML_w, XML_hyperlink ); |
| } |
| m_nHyperLinkCount.pop_back(); |
| |
| if (m_aRunSdt.m_bStartedSdt) |
| { |
| // Run-level SDT still open? Close it now. |
| m_aRunSdt.EndSdtBlock(m_pSerializer); |
| } |
| |
| if (m_bPageBreakAfter) |
| { |
| // tdf#128889 Trailing page break |
| SectionBreak(msword::PageBreak, false); |
| m_bPageBreakAfter = false; |
| } |
| |
| m_pSerializer->endElementNS( XML_w, XML_p ); |
| // on export sdt blocks are never nested ATM |
| if (!m_bAnchorLinkedToNode && !m_aParagraphSdt.m_bStartedSdt) |
| { |
| m_aParagraphSdt.WriteSdtBlock(m_pSerializer, m_bRunTextIsOn, m_rExport.SdrExporter().IsParagraphHasDrawing()); |
| |
| if (m_aParagraphSdt.m_bStartedSdt) |
| { |
| if (m_tableReference.m_bTableCellOpen) |
| m_tableReference.m_bTableCellParaSdtOpen = true; |
| if (m_rExport.SdrExporter().IsDMLAndVMLDrawingOpen()) |
| m_rExport.SdrExporter().setParagraphSdtOpen(true); |
| } |
| } |
| else |
| { |
| //These should be written out to the actual Node and not to the anchor. |
| //Clear them as they will be repopulated when the node is processed. |
| m_aParagraphSdt.m_nSdtPrToken = 0; |
| m_aParagraphSdt.DeleteAndResetTheLists(); |
| } |
| |
| m_pSerializer->mark(Tag_StartParagraph_2); |
| |
| // Write framePr |
| for ( const auto & pFrame : aFramePrTextbox ) |
| { |
| DocxTableExportContext aTableExportContext(*this); |
| m_aFramePr.SetFrame(pFrame.get(), !m_xTableWrt ? -1 : m_tableReference.m_nTableDepth); |
| m_rExport.SdrExporter().writeOnlyTextOfFrame(pFrame.get()); |
| m_aFramePr.SetFrame(nullptr); |
| } |
| |
| m_pSerializer->mergeTopMarks(Tag_StartParagraph_2, sax_fastparser::MergeMarks::PREPEND); |
| |
| //sdtcontent is written so Set m_bParagraphHasDrawing to false |
| m_rExport.SdrExporter().setParagraphHasDrawing(false); |
| m_bRunTextIsOn = false; |
| m_pSerializer->mergeTopMarks(Tag_StartParagraph_1); |
| |
| aFramePrTextbox.clear(); |
| // Check for end of cell, rows, tables here |
| FinishTableRowCell( pTextNodeInfoInner ); |
| |
| if( !m_rExport.SdrExporter().IsDMLAndVMLDrawingOpen() ) |
| m_bParagraphOpened = false; |
| |
| // Clear bookmarks of the current paragraph |
| m_aBookmarksOfParagraphStart.clear(); |
| m_aBookmarksOfParagraphEnd.clear(); |
| } |
| |
| #define MAX_CELL_IN_WORD 62 |
| |
| void DocxAttributeOutput::SyncNodelessCells(ww8::WW8TableNodeInfoInner::Pointer_t const & pInner, sal_Int32 nCell, sal_uInt32 nRow) |
| { |
| sal_Int32 nOpenCell = m_LastOpenCell.back(); |
| if (nOpenCell != -1 && nOpenCell != nCell && nOpenCell < MAX_CELL_IN_WORD) |
| EndTableCell(nOpenCell); |
| |
| sal_Int32 nClosedCell = m_LastClosedCell.back(); |
| for (sal_Int32 i = nClosedCell+1; i < nCell; ++i) |
| { |
| if (i >= MAX_CELL_IN_WORD) |
| break; |
| |
| if (i == 0) |
| StartTableRow(pInner); |
| |
| StartTableCell(pInner, i, nRow); |
| m_pSerializer->singleElementNS(XML_w, XML_p); |
| EndTableCell(i); |
| } |
| } |
| |
| void DocxAttributeOutput::FinishTableRowCell( ww8::WW8TableNodeInfoInner::Pointer_t const & pInner, bool bForceEmptyParagraph ) |
| { |
| if ( !pInner ) |
| return; |
| |
| // Where are we in the table |
| sal_uInt32 nRow = pInner->getRow(); |
| sal_Int32 nCell = pInner->getCell(); |
| |
| InitTableHelper( pInner ); |
| |
| // HACK |
| // msoffice seems to have an internal limitation of 63 columns for tables |
| // and refuses to load .docx with more, even though the spec seems to allow that; |
| // so simply if there are more columns, don't close the last one msoffice will handle |
| // and merge the contents of the remaining ones into it (since we don't close the cell |
| // here, following ones will not be opened) |
| const bool limitWorkaround = (nCell >= MAX_CELL_IN_WORD && !pInner->isEndOfLine()); |
| const bool bEndCell = pInner->isEndOfCell() && !limitWorkaround; |
| const bool bEndRow = pInner->isEndOfLine(); |
| |
| if (bEndCell) |
| { |
| while (pInner->getDepth() < m_tableReference.m_nTableDepth) |
| { |
| //we expect that the higher depth row was closed, and |
| //we are just missing the table close |
| assert(m_LastOpenCell.back() == -1 && m_LastClosedCell.back() == -1); |
| EndTable(); |
| } |
| |
| SyncNodelessCells(pInner, nCell, nRow); |
| |
| sal_Int32 nClosedCell = m_LastClosedCell.back(); |
| if (nCell == nClosedCell) |
| { |
| //Start missing trailing cell(s) |
| ++nCell; |
| StartTableCell(pInner, nCell, nRow); |
| |
| //Continue on missing next trailing cell(s) |
| ww8::RowSpansPtr xRowSpans = pInner->getRowSpansOfRow(); |
| sal_Int32 nRemainingCells = xRowSpans->size() - nCell; |
| for (sal_Int32 i = 1; i < nRemainingCells; ++i) |
| { |
| if (bForceEmptyParagraph) |
| { |
| m_pSerializer->singleElementNS(XML_w, XML_p); |
| } |
| |
| EndTableCell(nCell); |
| |
| StartTableCell(pInner, nCell, nRow); |
| } |
| } |
| |
| if (bForceEmptyParagraph) |
| { |
| m_pSerializer->singleElementNS(XML_w, XML_p); |
| } |
| |
| EndTableCell(nCell); |
| } |
| |
| // This is a line end |
| if (bEndRow) |
| EndTableRow(); |
| |
| // This is the end of the table |
| if (pInner->isFinalEndOfLine()) |
| EndTable(); |
| } |
| |
| void DocxAttributeOutput::EmptyParagraph() |
| { |
| m_pSerializer->singleElementNS(XML_w, XML_p); |
| } |
| |
| void DocxAttributeOutput::SectionBreaks(const SwNode& rNode) |
| { |
| // output page/section breaks |
| // Writer can have them at the beginning of a paragraph, or at the end, but |
| // in docx, we have to output them in the paragraph properties of the last |
| // paragraph in a section. To get it right, we have to switch to the next |
| // paragraph, and detect the section breaks there. |
| SwNodeIndex aNextIndex( rNode, 1 ); |
| |
| if (rNode.IsTextNode() || rNode.IsSectionNode()) |
| { |
| if (aNextIndex.GetNode().IsTextNode()) |
| { |
| const SwTextNode* pTextNode = static_cast<SwTextNode*>(&aNextIndex.GetNode()); |
| m_rExport.OutputSectionBreaks(pTextNode->GetpSwAttrSet(), *pTextNode, m_tableReference.m_bTableCellOpen); |
| } |
| else if (aNextIndex.GetNode().IsTableNode()) |
| { |
| const SwTableNode* pTableNode = static_cast<SwTableNode*>(&aNextIndex.GetNode()); |
| const SwFrameFormat *pFormat = pTableNode->GetTable().GetFrameFormat(); |
| m_rExport.OutputSectionBreaks(&(pFormat->GetAttrSet()), *pTableNode); |
| } |
| } |
| else if (rNode.IsEndNode()) |
| { |
| if (aNextIndex.GetNode().IsTextNode()) |
| { |
| // Handle section break between a table and a text node following it. |
| // Also handle section endings |
| const SwTextNode* pTextNode = aNextIndex.GetNode().GetTextNode(); |
| if (rNode.StartOfSectionNode()->IsTableNode() || rNode.StartOfSectionNode()->IsSectionNode()) |
| m_rExport.OutputSectionBreaks(pTextNode->GetpSwAttrSet(), *pTextNode, m_tableReference.m_bTableCellOpen); |
| } |
| else if (aNextIndex.GetNode().IsTableNode()) |
| { |
| // Handle section break between tables. |
| const SwTableNode* pTableNode = static_cast<SwTableNode*>(&aNextIndex.GetNode()); |
| const SwFrameFormat *pFormat = pTableNode->GetTable().GetFrameFormat(); |
| m_rExport.OutputSectionBreaks(&(pFormat->GetAttrSet()), *pTableNode); |
| } |
| } |
| } |
| |
| void DocxAttributeOutput::StartParagraphProperties() |
| { |
| m_pSerializer->mark(Tag_StartParagraphProperties); |
| |
| m_pSerializer->startElementNS(XML_w, XML_pPr); |
| |
| // and output the section break now (if it appeared) |
| if (m_pSectionInfo && m_rExport.m_nTextTyp == TXT_MAINTEXT) |
| { |
| m_rExport.SectionProperties( *m_pSectionInfo ); |
| m_pSectionInfo.reset(); |
| } |
| |
| InitCollectedParagraphProperties(); |
| } |
| |
| void DocxAttributeOutput::InitCollectedParagraphProperties() |
| { |
| m_pLRSpaceAttrList.clear(); |
| m_pParagraphSpacingAttrList.clear(); |
| |
| // Write the elements in the spec order |
| static const sal_Int32 aOrder[] = |
| { |
| FSNS( XML_w, XML_pStyle ), |
| FSNS( XML_w, XML_keepNext ), |
| FSNS( XML_w, XML_keepLines ), |
| FSNS( XML_w, XML_pageBreakBefore ), |
| FSNS( XML_w, XML_framePr ), |
| FSNS( XML_w, XML_widowControl ), |
| FSNS( XML_w, XML_numPr ), |
| FSNS( XML_w, XML_suppressLineNumbers ), |
| FSNS( XML_w, XML_pBdr ), |
| FSNS( XML_w, XML_shd ), |
| FSNS( XML_w, XML_tabs ), |
| FSNS( XML_w, XML_suppressAutoHyphens ), |
| FSNS( XML_w, XML_kinsoku ), |
| FSNS( XML_w, XML_wordWrap ), |
| FSNS( XML_w, XML_overflowPunct ), |
| FSNS( XML_w, XML_topLinePunct ), |
| FSNS( XML_w, XML_autoSpaceDE ), |
| FSNS( XML_w, XML_autoSpaceDN ), |
| FSNS( XML_w, XML_bidi ), |
| FSNS( XML_w, XML_adjustRightInd ), |
| FSNS( XML_w, XML_snapToGrid ), |
| FSNS( XML_w, XML_spacing ), |
| FSNS( XML_w, XML_ind ), |
| FSNS( XML_w, XML_contextualSpacing ), |
| FSNS( XML_w, XML_mirrorIndents ), |
| FSNS( XML_w, XML_suppressOverlap ), |
| FSNS( XML_w, XML_jc ), |
| FSNS( XML_w, XML_textDirection ), |
| FSNS( XML_w, XML_textAlignment ), |
| FSNS( XML_w, XML_textboxTightWrap ), |
| FSNS( XML_w, XML_outlineLvl ), |
| FSNS( XML_w, XML_divId ), |
| FSNS( XML_w, XML_cnfStyle ), |
| FSNS( XML_w, XML_rPr ), |
| FSNS( XML_w, XML_sectPr ), |
| FSNS( XML_w, XML_pPrChange ) |
| }; |
| |
| // postpone the output so that we can later [in EndParagraphProperties()] |
| // prepend the properties before the run |
| // coverity[overrun-buffer-arg : FALSE] - coverity has difficulty with css::uno::Sequence |
| m_pSerializer->mark(Tag_InitCollectedParagraphProperties, comphelper::containerToSequence(aOrder)); |
| } |
| |
| void DocxAttributeOutput::WriteCollectedParagraphProperties() |
| { |
| if ( m_rExport.SdrExporter().getFlyAttrList().is() ) |
| { |
| m_pSerializer->singleElementNS( XML_w, XML_framePr, |
| detachFrom(m_rExport.SdrExporter().getFlyAttrList() ) ); |
| } |
| |
| if (m_pLRSpaceAttrList.is()) |
| { |
| m_pSerializer->singleElementNS(XML_w, XML_ind, detachFrom(m_pLRSpaceAttrList)); |
| } |
| |
| if ( m_pParagraphSpacingAttrList.is() ) |
| { |
| m_pSerializer->singleElementNS( XML_w, XML_spacing, detachFrom( m_pParagraphSpacingAttrList ) ); |
| } |
| |
| if ( m_pBackgroundAttrList.is() ) |
| { |
| m_pSerializer->singleElementNS( XML_w, XML_shd, detachFrom( m_pBackgroundAttrList ) ); |
| m_aFramePr.SetUseFrameBackground(false); |
| } |
| } |
| |
| namespace |
| { |
| |
| /// Outputs an item set, that contains the formatting of the paragraph marker. |
| void lcl_writeParagraphMarkerProperties(DocxAttributeOutput& rAttributeOutput, const SfxItemSet& rParagraphMarkerProperties) |
| { |
| const SfxItemSet* pOldI = rAttributeOutput.GetExport().GetCurItemSet(); |
| rAttributeOutput.GetExport().SetCurItemSet(&rParagraphMarkerProperties); |
| |
| SfxWhichIter aIter(rParagraphMarkerProperties); |
| sal_uInt16 nWhichId = aIter.FirstWhich(); |
| const SfxPoolItem* pItem = nullptr; |
| // Did we already produce a <w:sz> element? |
| bool bFontSizeWritten = false; |
| bool bBoldWritten = false; |
| while (nWhichId) |
| { |
| if (aIter.GetItemState(true, &pItem) == SfxItemState::SET) |
| { |
| if (isCHRATR(nWhichId) || nWhichId == RES_TXTATR_CHARFMT) |
| { |
| // Will this item produce a <w:sz> element? |
| bool bFontSizeItem = nWhichId == RES_CHRATR_FONTSIZE || nWhichId == RES_CHRATR_CJK_FONTSIZE; |
| bool bBoldItem = nWhichId == RES_CHRATR_WEIGHT || nWhichId == RES_CHRATR_CJK_WEIGHT; |
| if (!(bFontSizeWritten && bFontSizeItem) && !(bBoldWritten && bBoldItem)) |
| rAttributeOutput.OutputItem(*pItem); |
| if (bFontSizeItem) |
| bFontSizeWritten = true; |
| if (bBoldItem) |
| bBoldWritten = true; |
| } |
| else if (nWhichId == RES_TXTATR_AUTOFMT) |
| { |
| const SwFormatAutoFormat* pAutoFormat = static_cast<const SwFormatAutoFormat*>(pItem); |
| lcl_writeParagraphMarkerProperties(rAttributeOutput, *pAutoFormat->GetStyleHandle()); |
| } |
| } |
| nWhichId = aIter.NextWhich(); |
| } |
| rAttributeOutput.GetExport().SetCurItemSet(pOldI); |
| } |
| |
| const char *RubyAlignValues[] = |
| { |
| "center", |
| "distributeLetter", |
| "distributeSpace", |
| "left", |
| "right", |
| "rightVertical" |
| }; |
| |
| |
| const char *lclConvertWW8JCToOOXMLRubyAlign(sal_Int32 nJC) |
| { |
| const sal_Int32 nElements = SAL_N_ELEMENTS(RubyAlignValues); |
| if ( nJC >=0 && nJC < nElements ) |
| return RubyAlignValues[nJC]; |
| return RubyAlignValues[0]; |
| } |
| |
| } |
| |
| void DocxAttributeOutput::EndParagraphProperties(const SfxItemSet& rParagraphMarkerProperties, const SwRedlineData* pRedlineData, const SwRedlineData* pRedlineParagraphMarkerDeleted, const SwRedlineData* pRedlineParagraphMarkerInserted) |
| { |
| // Call the 'Redline' function. This will add redline (change-tracking) information that regards to paragraph properties. |
| // This includes changes like 'Bold', 'Underline', 'Strikethrough' etc. |
| |
| // If there is RedlineData present, call WriteCollectedParagraphProperties() for writing pPr before calling Redline(). |
| // As there will be another pPr for redline and LO might mix both. |
| if(pRedlineData) |
| WriteCollectedParagraphProperties(); |
| Redline( pRedlineData ); |
| |
| WriteCollectedParagraphProperties(); |
| |
| // Merge the marks for the ordered elements |
| m_pSerializer->mergeTopMarks(Tag_InitCollectedParagraphProperties); |
| |
| // Write 'Paragraph Mark' properties |
| m_pSerializer->startElementNS(XML_w, XML_rPr); |
| // mark() before paragraph mark properties child elements. |
| InitCollectedRunProperties(); |
| |
| // The 'm_pFontsAttrList', 'm_pEastAsianLayoutAttrList', 'm_pCharLangAttrList' are used to hold information |
| // that should be collected by different properties in the core, and are all flushed together |
| // to the DOCX when the function 'WriteCollectedRunProperties' gets called. |
| // So we need to store the current status of these lists, so that we can revert back to them when |
| // we are done exporting the redline attributes. |
| auto pFontsAttrList_Original(detachFrom(m_pFontsAttrList)); |
| auto pEastAsianLayoutAttrList_Original(detachFrom(m_pEastAsianLayoutAttrList)); |
| auto pCharLangAttrList_Original(detachFrom(m_pCharLangAttrList)); |
| |
| lcl_writeParagraphMarkerProperties(*this, rParagraphMarkerProperties); |
| |
| // Write the collected run properties that are stored in 'm_pFontsAttrList', 'm_pEastAsianLayoutAttrList', 'm_pCharLangAttrList' |
| WriteCollectedRunProperties(); |
| |
| // Revert back the original values that were stored in 'm_pFontsAttrList', 'm_pEastAsianLayoutAttrList', 'm_pCharLangAttrList' |
| m_pFontsAttrList = std::move(pFontsAttrList_Original); |
| m_pEastAsianLayoutAttrList = std::move(pEastAsianLayoutAttrList_Original); |
| m_pCharLangAttrList = std::move(pCharLangAttrList_Original); |
| |
| if ( pRedlineParagraphMarkerDeleted ) |
| { |
| StartRedline( pRedlineParagraphMarkerDeleted, /*bLastRun=*/true ); |
| EndRedline( pRedlineParagraphMarkerDeleted, /*bLastRun=*/true ); |
| } |
| if ( pRedlineParagraphMarkerInserted ) |
| { |
| StartRedline( pRedlineParagraphMarkerInserted, /*bLastRun=*/true ); |
| EndRedline( pRedlineParagraphMarkerInserted, /*bLastRun=*/true ); |
| } |
| |
| // mergeTopMarks() after paragraph mark properties child elements. |
| m_pSerializer->mergeTopMarks(Tag_InitCollectedRunProperties); |
| m_pSerializer->endElementNS( XML_w, XML_rPr ); |
| |
| if (!m_bWritingHeaderFooter && m_aFramePr.Frame()) |
| { |
| const SwFrameFormat& rFrameFormat = m_aFramePr.Frame()->GetFrameFormat(); |
| assert(TextBoxIsFramePr(rFrameFormat) && "by definition, because Frame()"); |
| |
| const Size aSize = m_aFramePr.Frame()->GetSize(); |
| PopulateFrameProperties(&rFrameFormat, aSize); |
| |
| // if the paragraph itself never called FormatBox, do so now |
| if (m_aFramePr.UseFrameBorders(!m_xTableWrt ? -1 : m_tableReference.m_nTableDepth)) |
| FormatBox(rFrameFormat.GetBox()); |
| |
| if (m_aFramePr.UseFrameBackground()) |
| { |
| // The frame is usually imported as 100% transparent. Ignore in that case. |
| // Background only exports as fully opaque. Emulate - ignore transparency more than 50% |
| const SwAttrSet& rSet = rFrameFormat.GetAttrSet(); |
| const XFillStyleItem* pFillStyle(rSet.GetItem<XFillStyleItem>(XATTR_FILLSTYLE)); |
| if (pFillStyle && pFillStyle->GetValue() != drawing::FillStyle_NONE) |
| { |
| std::unique_ptr<SvxBrushItem> pBrush( |
| getSvxBrushItemFromSourceSet(rSet, RES_BACKGROUND)); |
| if (pBrush->GetColor().GetAlpha() > 127) // more opaque than transparent |
| { |
| FormatBackground(*pBrush); |
| WriteCollectedParagraphProperties(); |
| } |
| } |
| } |
| |
| if (m_aFramePr.UseFrameTextDirection(!m_xTableWrt ? -1 : m_tableReference.m_nTableDepth)) |
| { |
| const SvxFrameDirectionItem& rFrameDir = rFrameFormat.GetFrameDir(); |
| if (rFrameDir.GetValue() != SvxFrameDirection::Environment) |
| { |
| assert(!m_rExport.m_bOutPageDescs); |
| // hack: use existing variable to write out the full TextDirection attribute. |
| // This is valid for paragraphs/styles - just not native in LO, so hack for now. |
| m_rExport.m_bOutPageDescs = true; |
| FormatFrameDirection(rFrameDir); |
| m_rExport.m_bOutPageDescs = false; |
| } |
| } |
| |
| // reset to true in preparation for the next paragraph in the frame |
| m_aFramePr.SetUseFrameBorders(true); |
| m_aFramePr.SetUseFrameBackground(true); |
| m_aFramePr.SetUseFrameTextDirection(true); |
| } |
| |
| m_pSerializer->endElementNS( XML_w, XML_pPr ); |
| |
| // RDF metadata for this text node. |
| SwTextNode* pTextNode = m_rExport.m_pCurPam->GetPointNode().GetTextNode(); |
| std::map<OUString, OUString> aStatements; |
| if (pTextNode) |
| aStatements = SwRDFHelper::getTextNodeStatements(u"urn:bails"_ustr, *pTextNode); |
| if (!aStatements.empty()) |
| { |
| m_pSerializer->startElementNS(XML_w, XML_smartTag, |
| FSNS(XML_w, XML_uri), "http://www.w3.org/1999/02/22-rdf-syntax-ns#", |
| FSNS(XML_w, XML_element), "RDF"); |
| m_pSerializer->startElementNS(XML_w, XML_smartTagPr); |
| for (const auto& rStatement : aStatements) |
| m_pSerializer->singleElementNS(XML_w, XML_attr, |
| FSNS(XML_w, XML_name), rStatement.first, |
| FSNS(XML_w, XML_val), rStatement.second); |
| m_pSerializer->endElementNS(XML_w, XML_smartTagPr); |
| m_pSerializer->endElementNS(XML_w, XML_smartTag); |
| } |
| |
| if ((m_nColBreakStatus == COLBRK_WRITE || m_nColBreakStatus == COLBRK_WRITEANDPOSTPONE) |
| && !m_rExport.SdrExporter().IsDMLAndVMLDrawingOpen()) |
| { |
| m_pSerializer->startElementNS(XML_w, XML_r); |
| m_pSerializer->singleElementNS(XML_w, XML_br, FSNS(XML_w, XML_type), "column"); |
| m_pSerializer->endElementNS( XML_w, XML_r ); |
| |
| if ( m_nColBreakStatus == COLBRK_WRITEANDPOSTPONE ) |
| m_nColBreakStatus = COLBRK_POSTPONE; |
| else |
| m_nColBreakStatus = COLBRK_NONE; |
| } |
| |
| if (m_bPostponedPageBreak && !m_bWritingHeaderFooter |
| && !m_rExport.SdrExporter().IsDMLAndVMLDrawingOpen()) |
| { |
| m_pSerializer->startElementNS(XML_w, XML_r); |
| m_pSerializer->singleElementNS(XML_w, XML_br, FSNS(XML_w, XML_type), "page"); |
| m_pSerializer->endElementNS( XML_w, XML_r ); |
| |
| m_bPostponedPageBreak = false; |
| } |
| |
| // merge the properties _before_ the run (strictly speaking, just |
| // after the start of the paragraph) |
| m_pSerializer->mergeTopMarks(Tag_StartParagraphProperties, sax_fastparser::MergeMarks::PREPEND); |
| } |
| |
| void DocxAttributeOutput::SetStateOfFlyFrame( FlyProcessingState nStateOfFlyFrame ) |
| { |
| m_nStateOfFlyFrame = nStateOfFlyFrame; |
| } |
| |
| void DocxAttributeOutput::SetAnchorIsLinkedToNode( bool bAnchorLinkedToNode ) |
| { |
| m_bAnchorLinkedToNode = bAnchorLinkedToNode ; |
| } |
| |
| void DocxAttributeOutput::ResetFlyProcessingFlag() |
| { |
| m_bPostponedProcessingFly = false ; |
| } |
| |
| bool DocxAttributeOutput::IsFlyProcessingPostponed() |
| { |
| return m_bPostponedProcessingFly; |
| } |
| |
| void DocxAttributeOutput::StartRun( const SwRedlineData* pRedlineData, sal_Int32 /*nPos*/, bool /*bSingleEmptyRun*/ ) |
| { |
| // Don't start redline data here, possibly there is a hyperlink later, and |
| // that has to be started first. |
| m_pRedlineData = pRedlineData; |
| |
| // this mark is used to be able to enclose the run inside a sdr tag. |
| m_pSerializer->mark(Tag_StartRun_1); |
| |
| // postpone the output of the start of a run (there are elements that need |
| // to be written before the start of the run, but we learn which they are |
| // _inside_ of the run) |
| m_pSerializer->mark(Tag_StartRun_2); // let's call it "postponed run start" |
| |
| // postpone the output of the text (we get it before the run properties, |
| // but must write it after them) |
| m_pSerializer->mark(Tag_StartRun_3); // let's call it "postponed text" |
| } |
| |
| void DocxAttributeOutput::EndRun(const SwTextNode* pNode, sal_Int32 nPos, sal_Int32 nLen, bool bLastRun) |
| { |
| int nFieldsInPrevHyperlink = m_nFieldsInHyperlink; |
| // Reset m_nFieldsInHyperlink if a new hyperlink is about to start |
| if ( m_pHyperlinkAttrList.is() ) |
| { |
| m_nFieldsInHyperlink = 0; |
| } |
| |
| // Write field starts |
| for ( std::vector<FieldInfos>::iterator pIt = m_Fields.begin() + nFieldsInPrevHyperlink; pIt != m_Fields.end(); ) |
| { |
| // Add the fields starts for all but hyperlinks and TOCs |
| if (pIt->bOpen && pIt->pField && pIt->eType != ww::eFORMDROPDOWN && |
| // it is not an input field with extra grabbag params (sdt field) |
| (!(pIt->eType == ww::eFILLIN && static_cast<const SwInputField*>(pIt->pField.get())->getGrabBagParams().hasElements())) |
| ) |
| { |
| StartField_Impl( pNode, nPos, *pIt ); |
| |
| // Remove the field from the stack if only the start has to be written |
| // Unknown fields should be removed too |
| if ( !pIt->bClose || ( pIt->eType == ww::eUNKNOWN ) ) |
| { |
| pIt = m_Fields.erase( pIt ); |
| continue; |
| } |
| |
| if (m_nHyperLinkCount.back() > 0 || m_pHyperlinkAttrList.is()) |
| { |
| ++m_nFieldsInHyperlink; |
| } |
| } |
| ++pIt; |
| } |
| |
| // write the run properties + the text, already in the correct order |
| m_pSerializer->mergeTopMarks(Tag_StartRun_3); // merges with "postponed text", see above |
| |
| // level down, to be able to prepend the actual run start attribute (just |
| // before "postponed run start") |
| m_pSerializer->mark(Tag_EndRun_1); // let's call it "actual run start" |
| bool bCloseEarlierSDT = false; |
| |
| if (m_bEndCharSdt) |
| { |
| // This is the common case: "close sdt before the current run" was requested by the next run. |
| |
| // if another sdt starts in this run, then wait |
| // as closing the sdt now, might cause nesting of sdts |
| if (m_aRunSdt.m_nSdtPrToken > 0) |
| bCloseEarlierSDT = true; |
| else |
| m_aRunSdt.EndSdtBlock(m_pSerializer); |
| m_bEndCharSdt = false; |
| } |
| |
| if ( m_closeHyperlinkInPreviousRun ) |
| { |
| if (m_nHyperLinkCount.back() > 0) |
| { |
| for ( int i = 0; i < nFieldsInPrevHyperlink; i++ ) |
| { |
| // If fields begin before hyperlink then |
| // it should end before hyperlink close |
| EndField_Impl( pNode, nPos, m_Fields.back( ) ); |
| m_Fields.pop_back(); |
| } |
| m_pSerializer->endElementNS( XML_w, XML_hyperlink ); |
| m_endPageRef = false; |
| m_nHyperLinkCount.back()--; |
| m_closeHyperlinkInPreviousRun = false; |
| } |
| else |
| { |
| bool bIsStartedHyperlink = false; |
| for (const sal_Int32 nLinkCount : m_nHyperLinkCount) |
| { |
| if (nLinkCount > 0) |
| { |
| bIsStartedHyperlink = true; |
| break; |
| } |
| } |
| if (!bIsStartedHyperlink) |
| m_closeHyperlinkInPreviousRun = false; |
| } |
| } |
| |
| // Write the hyperlink and toc fields starts |
| for ( std::vector<FieldInfos>::iterator pIt = m_Fields.begin(); pIt != m_Fields.end(); ) |
| { |
| // Add the fields starts for hyperlinks, TOCs and index marks |
| if (pIt->bOpen && (!pIt->pField || pIt->eType == ww::eFORMDROPDOWN || |
| // InputField with extra grabbag params - it is sdt field |
| (pIt->eType == ww::eFILLIN && static_cast<const SwInputField*>(pIt->pField.get())->getGrabBagParams().hasElements()))) |
| { |
| StartRedline( m_pRedlineData, bLastRun ); |
| StartField_Impl( pNode, nPos, *pIt, true ); |
| EndRedline( m_pRedlineData, bLastRun ); |
| |
| if (m_nHyperLinkCount.back() > 0) |
| ++m_nFieldsInHyperlink; |
| |
| // Remove the field if no end needs to be written |
| if (!pIt->bSep) |
| { |
| pIt = m_Fields.erase( pIt ); |
| continue; |
| } |
| } |
| if (pIt->bSep && !pIt->pField) |
| { |
| // for TOXMark: |
| // Word ignores bookmarks in field result that is empty; |
| // work around this by writing bookmark into field command. |
| if (!m_sFieldBkm.isEmpty()) |
| { |
| DoWriteBookmarkTagStart(m_sFieldBkm); |
| DoWriteBookmarkTagEnd(m_nNextBookmarkId); |
| m_nNextBookmarkId++; |
| m_sFieldBkm.clear(); |
| } |
| CmdEndField_Impl(pNode, nPos, true); |
| // Remove the field if no end needs to be written |
| if (!pIt->bClose) |
| { |
| pIt = m_Fields.erase( pIt ); |
| continue; |
| } |
| } |
| ++pIt; |
| } |
| |
| // Start the hyperlink after the fields separators or we would generate invalid file |
| bool newStartedHyperlink(false); |
| if ( m_pHyperlinkAttrList.is() ) |
| { |
| // if we are ending a hyperlink and there's another one starting here, |
| // don't do this, so that the fields are closed further down when |
| // the end hyperlink is handled, which is more likely to put the end in |
| // the right place, as far as i can tell (not very far in this muck) |
| if (!m_closeHyperlinkInThisRun) |
| { |
| // end ToX fields that want to end _before_ starting the hyperlink |
| for (auto it = m_Fields.rbegin(); it != m_Fields.rend(); ) |
| { |
| if (it->bClose && !it->pField) |
| { |
| EndField_Impl( pNode, nPos, *it ); |
| it = decltype(m_Fields)::reverse_iterator(m_Fields.erase(it.base() - 1)); |
| } |
| else |
| { |
| ++it; |
| } |
| } |
| } |
| newStartedHyperlink = true; |
| |
| m_pSerializer->startElementNS( XML_w, XML_hyperlink, detachFrom( m_pHyperlinkAttrList ) ); |
| m_nHyperLinkCount.back()++; |
| } |
| |
| // if there is some redlining in the document, output it |
| bool bSkipRedline = false; |
| if (nLen == 1) |
| { |
| // Don't redline content-controls--Word doesn't do them. |
| SwTextAttr* pAttr |
| = pNode->GetTextAttrAt(nPos, RES_TXTATR_CONTENTCONTROL, sw::GetTextAttrMode::Default); |
| if (pAttr && pAttr->GetStart() == nPos) |
| { |
| bSkipRedline = true; |
| } |
| } |
| |
| if (!bSkipRedline) |
| { |
| StartRedline(m_pRedlineData, bLastRun); |
| } |
| |
| // XML_r node should be surrounded with bookmark-begin and bookmark-end nodes if it has bookmarks. |
| // The same is applied for permission ranges. |
| // But due to unit test "testFdo85542" let's output bookmark-begin with bookmark-end. |
| DoWriteBookmarksStart(m_rBookmarksStart, m_pMoveRedlineData); |
| DoWriteBookmarksEnd(m_rBookmarksEnd); |
| DoWritePermissionsStart(); |
| DoWriteAnnotationMarks(); |
| |
| if (m_closeHyperlinkInThisRun && m_nHyperLinkCount.back() > 0 && !m_hyperLinkAnchor.isEmpty() |
| && m_hyperLinkAnchor.startsWith("_Toc")) |
| { |
| OUString sToken; |
| m_pSerializer->startElementNS(XML_w, XML_r); |
| m_pSerializer->startElementNS(XML_w, XML_rPr); |
| m_pSerializer->singleElementNS(XML_w, XML_webHidden); |
| m_pSerializer->endElementNS( XML_w, XML_rPr ); |
| m_pSerializer->startElementNS(XML_w, XML_fldChar, FSNS(XML_w, XML_fldCharType), "begin"); |
| m_pSerializer->endElementNS( XML_w, XML_fldChar ); |
| m_pSerializer->endElementNS( XML_w, XML_r ); |
| |
| |
| m_pSerializer->startElementNS(XML_w, XML_r); |
| m_pSerializer->startElementNS(XML_w, XML_rPr); |
| m_pSerializer->singleElementNS(XML_w, XML_webHidden); |
| m_pSerializer->endElementNS( XML_w, XML_rPr ); |
| sToken = "PAGEREF " + m_hyperLinkAnchor + " \\h"; // '\h' Creates a hyperlink to the bookmarked paragraph. |
| DoWriteCmd( sToken ); |
| m_pSerializer->endElementNS( XML_w, XML_r ); |
| |
| // Write the Field separator |
| m_pSerializer->startElementNS(XML_w, XML_r); |
| m_pSerializer->startElementNS(XML_w, XML_rPr); |
| m_pSerializer->singleElementNS(XML_w, XML_webHidden); |
| m_pSerializer->endElementNS( XML_w, XML_rPr ); |
| m_pSerializer->singleElementNS( XML_w, XML_fldChar, |
| FSNS( XML_w, XML_fldCharType ), "separate" ); |
| m_pSerializer->endElementNS( XML_w, XML_r ); |
| // At start of every "PAGEREF" field m_endPageRef value should be true. |
| m_endPageRef = true; |
| } |
| |
| DoWriteBookmarkStartIfExist(nPos); |
| |
| if (nLen != -1) |
| { |
| SwTextAttr* pAttr = pNode->GetTextAttrAt(nPos, RES_TXTATR_CONTENTCONTROL, ::sw::GetTextAttrMode::Default); |
| if (pAttr && pAttr->GetStart() == nPos) |
| { |
| auto pTextContentControl = static_txtattr_cast<SwTextContentControl*>(pAttr); |
| m_pContentControl = pTextContentControl->GetContentControl().GetContentControl(); |
| if (!m_tableReference.m_bTableCellChanged) |
| { |
| WriteContentControlStart(); |
| } |
| } |
| } |
| |
| m_pSerializer->startElementNS(XML_w, XML_r); |
| if(GetExport().m_bTabInTOC && m_pHyperlinkAttrList.is()) |
| { |
| RunText(u"\t"_ustr) ; |
| } |
| m_pSerializer->mergeTopMarks(Tag_EndRun_1, sax_fastparser::MergeMarks::PREPEND); // merges with "postponed run start", see above |
| |
| if ( !m_sRawText.isEmpty() ) |
| { |
| RunText( m_sRawText ); |
| m_sRawText.clear(); |
| } |
| |
| // write the run start + the run content |
| m_pSerializer->mergeTopMarks(Tag_StartRun_2); // merges the "actual run start" |
| // append the actual run end |
| m_pSerializer->endElementNS( XML_w, XML_r ); |
| |
| // if there is some redlining in the document, output it |
| // (except in the case of fields with multiple runs) |
| if (!bSkipRedline) |
| { |
| EndRedline(m_pRedlineData, bLastRun); |
| } |
| |
| if (nLen != -1) |
| { |
| sal_Int32 nEnd = nPos + nLen; |
| SwTextAttr* pAttr = pNode->GetTextAttrAt(nPos, RES_TXTATR_CONTENTCONTROL, ::sw::GetTextAttrMode::Default); |
| if (pAttr && *pAttr->GetEnd() == nEnd && !m_tableReference.m_bTableCellChanged) |
| { |
| WriteContentControlEnd(); |
| } |
| } |
| |
| // enclose in a sdt block, if necessary: if one is already started, then don't do it for now |
| // (so on export sdt blocks are never nested ATM) |
| if ( !m_bAnchorLinkedToNode && !m_aRunSdt.m_bStartedSdt) |
| { |
| m_aRunSdt.WriteSdtBlock(m_pSerializer, m_bRunTextIsOn, m_rExport.SdrExporter().IsParagraphHasDrawing()); |
| } |
| else |
| { |
| //These should be written out to the actual Node and not to the anchor. |
| //Clear them as they will be repopulated when the node is processed. |
| m_aRunSdt.m_nSdtPrToken = 0; |
| m_aRunSdt.DeleteAndResetTheLists(); |
| } |
| |
| if (bCloseEarlierSDT) |
| { |
| m_pSerializer->mark(Tag_EndRun_2); |
| m_aRunSdt.EndSdtBlock(m_pSerializer); |
| m_pSerializer->mergeTopMarks(Tag_EndRun_2, sax_fastparser::MergeMarks::PREPEND); |
| } |
| |
| m_pSerializer->mergeTopMarks(Tag_StartRun_1); |
| |
| // XML_r node should be surrounded with permission-begin and permission-end nodes if it has permission. |
| DoWritePermissionsEnd(); |
| |
| for (const auto& rpMath : m_aPostponedMaths) |
| WritePostponedMath(rpMath.pMathObject, rpMath.nMathObjAlignment); |
| m_aPostponedMaths.clear(); |
| |
| for (const auto& rpControl : m_aPostponedFormControls) |
| WritePostponedFormControl(rpControl); |
| m_aPostponedFormControls.clear(); |
| |
| WritePostponedActiveXControl(false); |
| |
| WritePendingPlaceholder(); |
| |
| if ( !m_bWritingField ) |
| { |
| m_pRedlineData = nullptr; |
| } |
| |
| if ( m_closeHyperlinkInThisRun ) |
| { |
| if (m_nHyperLinkCount.back() > 0) |
| { |
| if( m_endPageRef ) |
| { |
| // Hyperlink is started and fldchar "end" needs to be written for PAGEREF |
| m_pSerializer->startElementNS(XML_w, XML_r); |
| m_pSerializer->startElementNS(XML_w, XML_rPr); |
| m_pSerializer->singleElementNS(XML_w, XML_webHidden); |
| m_pSerializer->endElementNS( XML_w, XML_rPr ); |
| m_pSerializer->singleElementNS( XML_w, XML_fldChar, |
| FSNS( XML_w, XML_fldCharType ), "end" ); |
| m_pSerializer->endElementNS( XML_w, XML_r ); |
| m_endPageRef = false; |
| m_hyperLinkAnchor.clear(); |
| } |
| for ( int i = 0; i < m_nFieldsInHyperlink; i++ ) |
| { |
| // If fields begin after hyperlink start then |
| // it should end before hyperlink close |
| EndField_Impl( pNode, nPos, m_Fields.back( ) ); |
| m_Fields.pop_back(); |
| } |
| m_nFieldsInHyperlink = 0; |
| |
| m_pSerializer->endElementNS( XML_w, XML_hyperlink ); |
| m_nHyperLinkCount.back()--; |
| m_closeHyperlinkInThisRun = false; |
| } |
| else |
| { |
| bool bIsStartedHyperlink = false; |
| for (const sal_Int32 nLinkCount : m_nHyperLinkCount) |
| { |
| if (nLinkCount > 0) |
| { |
| bIsStartedHyperlink = true; |
| break; |
| } |
| } |
| if (!bIsStartedHyperlink) |
| m_closeHyperlinkInThisRun = false; |
| } |
| } |
| |
| if (!newStartedHyperlink) |
| { |
| while ( m_Fields.begin() != m_Fields.end() ) |
| { |
| EndField_Impl( pNode, nPos, m_Fields.front( ) ); |
| m_Fields.erase( m_Fields.begin( ) ); |
| } |
| m_nFieldsInHyperlink = 0; |
| } |
| |
| // end ToX fields |
| for (auto it = m_Fields.rbegin(); it != m_Fields.rend(); ) |
| { |
| if (it->bClose && !it->pField) |
| { |
| EndField_Impl( pNode, nPos, *it ); |
| it = decltype(m_Fields)::reverse_iterator(m_Fields.erase(it.base() - 1)); |
| } |
| else |
| { |
| ++it; |
| } |
| } |
| |
| if ( m_pRedlineData ) |
| { |
| EndRedline( m_pRedlineData, bLastRun ); |
| m_pRedlineData = nullptr; |
| } |
| |
| DoWriteBookmarksStart(m_rFinalBookmarksStart); |
| DoWriteBookmarksEnd(m_rFinalBookmarksEnd); |
| DoWriteBookmarkEndIfExist(nPos); |
| } |
| |
| void DocxAttributeOutput::DoWriteBookmarkTagStart(const OUString& bookmarkName) |
| { |
| m_pSerializer->singleElementNS(XML_w, XML_bookmarkStart, |
| FSNS(XML_w, XML_id), OString::number(m_nNextBookmarkId), |
| FSNS(XML_w, XML_name), GetExport().BookmarkToWord(bookmarkName)); |
| } |
| |
| void DocxAttributeOutput::DoWriteBookmarkTagEnd(sal_Int32 const nId) |
| { |
| m_pSerializer->singleElementNS(XML_w, XML_bookmarkEnd, |
| FSNS(XML_w, XML_id), OString::number(nId)); |
| } |
| |
| void DocxAttributeOutput::DoWriteMoveRangeTagStart(std::u16string_view bookmarkName, |
| bool bFrom, const SwRedlineData* pRedlineData) |
| { |
| bool bRemovePersonalInfo = SvtSecurityOptions::IsOptionSet( |
| SvtSecurityOptions::EOption::DocWarnRemovePersonalInfo ) && !SvtSecurityOptions::IsOptionSet( |
| SvtSecurityOptions::EOption::DocWarnKeepRedlineInfo); |
| |
| const OUString &rAuthor( SW_MOD()->GetRedlineAuthor( pRedlineData->GetAuthor() ) ); |
| const DateTime aDateTime = pRedlineData->GetTimeStamp(); |
| bool bNoDate = bRemovePersonalInfo || |
| ( aDateTime.GetYear() == 1970 && aDateTime.GetMonth() == 1 && aDateTime.GetDay() == 1 ); |
| |
| rtl::Reference<sax_fastparser::FastAttributeList> pAttributeList |
| = sax_fastparser::FastSerializerHelper::createAttrList(); |
| |
| pAttributeList->add(FSNS(XML_w, XML_id), OString::number(m_nNextBookmarkId)); |
| pAttributeList->add(FSNS(XML_w, XML_author ), bRemovePersonalInfo |
| ? "Author" + OString::number( GetExport().GetInfoID(rAuthor) ) |
| : OUStringToOString(rAuthor, RTL_TEXTENCODING_UTF8)); |
| if (!bNoDate) |
| pAttributeList->add(FSNS(XML_w, XML_date ), DateTimeToOString( aDateTime )); |
| pAttributeList->add(FSNS(XML_w, XML_name), bookmarkName); |
| m_pSerializer->singleElementNS( XML_w, bFrom ? XML_moveFromRangeStart : XML_moveToRangeStart, pAttributeList ); |
| |
| // tdf#150166 avoid of unpaired moveRangeEnd at moved ToC |
| m_rSavedBookmarksIds.insert(m_nNextBookmarkId); |
| } |
| |
| void DocxAttributeOutput::DoWriteMoveRangeTagEnd(sal_Int32 const nId, bool bFrom) |
| { |
| if ( m_rSavedBookmarksIds.count(nId) ) |
| { |
| m_pSerializer->singleElementNS(XML_w, bFrom |
| ? XML_moveFromRangeEnd |
| : XML_moveToRangeEnd, |
| FSNS(XML_w, XML_id), OString::number(nId)); |
| |
| m_rSavedBookmarksIds.erase(nId); |
| } |
| } |
| |
| void DocxAttributeOutput::DoWriteBookmarkStartIfExist(sal_Int32 nRunPos) |
| { |
| auto aRange = m_aBookmarksOfParagraphStart.equal_range(nRunPos); |
| for( auto aIter = aRange.first; aIter != aRange.second; ++aIter) |
| { |
| DoWriteBookmarkTagStart(aIter->second); |
| m_rOpenedBookmarksIds[aIter->second] = m_nNextBookmarkId; |
| m_sLastOpenedBookmark = GetExport().BookmarkToWord(aIter->second); |
| m_nNextBookmarkId++; |
| } |
| } |
| |
| void DocxAttributeOutput::DoWriteBookmarkEndIfExist(sal_Int32 nRunPos) |
| { |
| auto aRange = m_aBookmarksOfParagraphEnd.equal_range(nRunPos); |
| for( auto aIter = aRange.first; aIter != aRange.second; ++aIter) |
| { |
| // Get the id of the bookmark |
| auto pPos = m_rOpenedBookmarksIds.find(aIter->second); |
| if (pPos != m_rOpenedBookmarksIds.end()) |
| { |
| // Output the bookmark |
| DoWriteBookmarkTagEnd(pPos->second); |
| m_rOpenedBookmarksIds.erase(aIter->second); |
| } |
| } |
| } |
| |
| /// Write the start bookmarks |
| void DocxAttributeOutput::DoWriteBookmarksStart(std::vector<OUString>& rStarts, const SwRedlineData* pRedlineData) |
| { |
| for (const OUString & bookmarkName : rStarts) |
| { |
| // Output the bookmark (including MoveBookmark of the tracked moving) |
| bool bMove = false; |
| bool bFrom = false; |
| OUString sBookmarkName = GetExport().BookmarkToWord(bookmarkName, &bMove, &bFrom); |
| if ( bMove ) |
| { |
| // TODO: redline data of MoveBookmark is restored from the first redline of the bookmark |
| // range. But a later deletion within a tracked moving is still imported as plain |
| // deletion, so check IsMoved() and skip the export of the tracked moving to avoid |
| // export with bad author or date |
| if ( pRedlineData && pRedlineData->IsMoved() ) |
| DoWriteMoveRangeTagStart(sBookmarkName, bFrom, pRedlineData); |
| } |
| else |
| DoWriteBookmarkTagStart(bookmarkName); |
| |
| m_rOpenedBookmarksIds[bookmarkName] = m_nNextBookmarkId; |
| m_sLastOpenedBookmark = sBookmarkName; |
| m_nNextBookmarkId++; |
| } |
| rStarts.clear(); |
| } |
| |
| /// export the end bookmarks |
| void DocxAttributeOutput::DoWriteBookmarksEnd(std::vector<OUString>& rEnds) |
| { |
| for (const OUString & bookmarkName : rEnds) |
| { |
| // Get the id of the bookmark |
| auto pPos = m_rOpenedBookmarksIds.find(bookmarkName); |
| |
| if (pPos != m_rOpenedBookmarksIds.end()) |
| { |
| bool bMove = false; |
| bool bFrom = false; |
| GetExport().BookmarkToWord(bookmarkName, &bMove, &bFrom); |
| // Output the bookmark (including MoveBookmark of the tracked moving) |
| if ( bMove ) |
| DoWriteMoveRangeTagEnd(pPos->second, bFrom); |
| else |
| DoWriteBookmarkTagEnd(pPos->second); |
| |
| m_rOpenedBookmarksIds.erase(bookmarkName); |
| } |
| } |
| rEnds.clear(); |
| } |
| |
| // For construction of the special bookmark name template for permissions: |
| // see, PermInsertPosition::createBookmarkName() |
| // |
| // Syntax: |
| // - "permission-for-user:<permission-id>:<permission-user-name>" |
| // - "permission-for-group:<permission-id>:<permission-group-name>" |
| // |
| void DocxAttributeOutput::DoWritePermissionTagStart(std::u16string_view permission) |
| { |
| std::u16string_view permissionIdAndName; |
| |
| if (o3tl::starts_with(permission, u"permission-for-group:", &permissionIdAndName)) |
| { |
| const std::size_t separatorIndex = permissionIdAndName.find(u':'); |
| assert(separatorIndex != std::u16string_view::npos); |
| const OUString permissionId(permissionIdAndName.substr(0, separatorIndex)); |
| const OUString permissionName(permissionIdAndName.substr(separatorIndex + 1)); |
| |
| m_pSerializer->singleElementNS(XML_w, XML_permStart, |
| FSNS(XML_w, XML_id), GetExport().BookmarkToWord(permissionId), |
| FSNS(XML_w, XML_edGrp), GetExport().BookmarkToWord(permissionName)); |
| } |
| else |
| { |
| auto const ok = o3tl::starts_with( |
| permission, u"permission-for-user:", &permissionIdAndName); |
| assert(ok); (void)ok; |
| const std::size_t separatorIndex = permissionIdAndName.find(u':'); |
| assert(separatorIndex != std::u16string_view::npos); |
| const OUString permissionId(permissionIdAndName.substr(0, separatorIndex)); |
| const OUString permissionName(permissionIdAndName.substr(separatorIndex + 1)); |
| |
| m_pSerializer->singleElementNS(XML_w, XML_permStart, |
| FSNS(XML_w, XML_id), GetExport().BookmarkToWord(permissionId), |
| FSNS(XML_w, XML_ed), GetExport().BookmarkToWord(permissionName)); |
| } |
| } |
| |
| |
| // For construction of the special bookmark name template for permissions: |
| // see, PermInsertPosition::createBookmarkName() |
| // |
| // Syntax: |
| // - "permission-for-user:<permission-id>:<permission-user-name>" |
| // - "permission-for-group:<permission-id>:<permission-group-name>" |
| // |
| void DocxAttributeOutput::DoWritePermissionTagEnd(std::u16string_view permission) |
| { |
| std::u16string_view permissionIdAndName; |
| |
| auto const ok = o3tl::starts_with(permission, u"permission-for-group:", &permissionIdAndName) || |
| o3tl::starts_with(permission, u"permission-for-user:", &permissionIdAndName); |
| assert(ok); (void)ok; |
| |
| const std::size_t separatorIndex = permissionIdAndName.find(u':'); |
| assert(separatorIndex != std::u16string_view::npos); |
| const OUString permissionId(permissionIdAndName.substr(0, separatorIndex)); |
| |
| m_pSerializer->singleElementNS(XML_w, XML_permEnd, |
| FSNS(XML_w, XML_id), GetExport().BookmarkToWord(permissionId)); |
| } |
| |
| /// Write the start permissions |
| void DocxAttributeOutput::DoWritePermissionsStart() |
| { |
| for (const OUString & permission : m_rPermissionsStart) |
| { |
| DoWritePermissionTagStart(permission); |
| } |
| m_rPermissionsStart.clear(); |
| } |
| |
| /// export the end permissions |
| void DocxAttributeOutput::DoWritePermissionsEnd() |
| { |
| for (const OUString & permission : m_rPermissionsEnd) |
| { |
| DoWritePermissionTagEnd(permission); |
| } |
| m_rPermissionsEnd.clear(); |
| } |
| |
| void DocxAttributeOutput::DoWriteAnnotationMarks() |
| { |
| // Write the start annotation marks |
| for ( const auto & rName : m_rAnnotationMarksStart ) |
| { |
| // Output the annotation mark |
| /* Ensure that the existing Annotation Marks are not overwritten |
| as it causes discrepancy when DocxAttributeOutput::PostitField |
| refers to this map & while mapping comment id's in document.xml & |
| comment.xml. |
| */ |
| if ( m_rOpenedAnnotationMarksIds.end() == m_rOpenedAnnotationMarksIds.find( rName ) ) |
| { |
| const sal_Int32 nId = m_nNextAnnotationMarkId++; |
| m_rOpenedAnnotationMarksIds[rName] = nId; |
| m_pSerializer->singleElementNS( XML_w, XML_commentRangeStart, |
| FSNS( XML_w, XML_id ), OString::number(nId) ); |
| m_sLastOpenedAnnotationMark = rName; |
| } |
| } |
| m_rAnnotationMarksStart.clear(); |
| |
| // export the end annotation marks |
| for ( const auto & rName : m_rAnnotationMarksEnd ) |
| { |
| // Get the id of the annotation mark |
| auto pPos = m_rOpenedAnnotationMarksIds.find( rName ); |
| if ( pPos != m_rOpenedAnnotationMarksIds.end( ) ) |
| { |
| const sal_Int32 nId = ( *pPos ).second; |
| m_pSerializer->singleElementNS( XML_w, XML_commentRangeEnd, |
| FSNS( XML_w, XML_id ), OString::number(nId) ); |
| m_rOpenedAnnotationMarksIds.erase( rName ); |
| |
| m_pSerializer->startElementNS(XML_w, XML_r); |
| m_pSerializer->singleElementNS( XML_w, XML_commentReference, FSNS( XML_w, XML_id ), |
| OString::number(nId) ); |
| m_pSerializer->endElementNS(XML_w, XML_r); |
| } |
| } |
| m_rAnnotationMarksEnd.clear(); |
| } |
| |
| void DocxAttributeOutput::WriteFFData( const FieldInfos& rInfos ) |
| { |
| const ::sw::mark::IFieldmark& rFieldmark = *rInfos.pFieldmark; |
| FieldMarkParamsHelper params( rFieldmark ); |
| |
| OUString sEntryMacro; |
| params.extractParam(u"EntryMacro"_ustr, sEntryMacro); |
| OUString sExitMacro; |
| params.extractParam(u"ExitMacro"_ustr, sExitMacro); |
| OUString sHelp; |
| params.extractParam(u"Help"_ustr, sHelp); |
| OUString sHint; |
| params.extractParam(u"Hint"_ustr, sHint); // .docx StatusText |
| if ( sHint.isEmpty() ) |
| params.extractParam(u"Description"_ustr, sHint); // .doc StatusText |
| |
| if ( rInfos.eType == ww::eFORMDROPDOWN ) |
| { |
| uno::Sequence< OUString> vListEntries; |
| OUString sName, sSelected; |
| |
| params.extractParam( ODF_FORMDROPDOWN_LISTENTRY, vListEntries ); |
| if (vListEntries.getLength() > ODF_FORMDROPDOWN_ENTRY_COUNT_LIMIT) |
| vListEntries = uno::Sequence< OUString>(vListEntries.getArray(), ODF_FORMDROPDOWN_ENTRY_COUNT_LIMIT); |
| |
| sName = params.getName(); |
| sal_Int32 nSelectedIndex = 0; |
| |
| if ( params.extractParam( ODF_FORMDROPDOWN_RESULT, nSelectedIndex ) ) |
| { |
| if (nSelectedIndex < vListEntries.getLength() ) |
| sSelected = vListEntries[ nSelectedIndex ]; |
| } |
| |
| GetExport().DoComboBox( sName, OUString(), OUString(), sSelected, vListEntries ); |
| } |
| else if ( rInfos.eType == ww::eFORMCHECKBOX ) |
| { |
| const OUString sName = params.getName(); |
| bool bChecked = false; |
| |
| const sw::mark::ICheckboxFieldmark* pCheckboxFm = dynamic_cast<const sw::mark::ICheckboxFieldmark*>(&rFieldmark); |
| if ( pCheckboxFm && pCheckboxFm->IsChecked() ) |
| bChecked = true; |
| |
| FFDataWriterHelper ffdataOut( m_pSerializer ); |
| ffdataOut.WriteFormCheckbox( sName, sEntryMacro, sExitMacro, sHelp, sHint, bChecked ); |
| } |
| else if ( rInfos.eType == ww::eFORMTEXT ) |
| { |
| OUString sType; |
| params.extractParam(u"Type"_ustr, sType); |
| OUString sDefaultText; |
| params.extractParam(u"Content"_ustr, sDefaultText); |
| sal_uInt16 nMaxLength = 0; |
| params.extractParam(u"MaxLength"_ustr, nMaxLength); |
| OUString sFormat; |
| params.extractParam(u"Format"_ustr, sFormat); |
| FFDataWriterHelper ffdataOut( m_pSerializer ); |
| ffdataOut.WriteFormText( params.getName(), sEntryMacro, sExitMacro, sHelp, sHint, |
| sType, sDefaultText, nMaxLength, sFormat ); |
| } |
| } |
| |
| void DocxAttributeOutput::WriteFormDateStart(const OUString& sFullDate, const OUString& sDateFormat, const OUString& sLang, const uno::Sequence<beans::PropertyValue>& aGrabBagSdt) |
| { |
| m_pSerializer->startElementNS(XML_w, XML_sdt); |
| m_pSerializer->startElementNS(XML_w, XML_sdtPr); |
| |
| if(!sFullDate.isEmpty()) |
| m_pSerializer->startElementNS(XML_w, XML_date, FSNS(XML_w, XML_fullDate), sFullDate); |
| else |
| m_pSerializer->startElementNS(XML_w, XML_date); |
| |
| // Replace quotation mark used for marking static strings in date format |
| OUString sDateFormat1 = sDateFormat.replaceAll("\"", "'"); |
| m_pSerializer->singleElementNS(XML_w, XML_dateFormat, |
| FSNS(XML_w, XML_val), sDateFormat1); |
| m_pSerializer->singleElementNS(XML_w, XML_lid, |
| FSNS(XML_w, XML_val), sLang); |
| m_pSerializer->singleElementNS(XML_w, XML_storeMappedDataAs, |
| FSNS(XML_w, XML_val), "dateTime"); |
| m_pSerializer->singleElementNS(XML_w, XML_calendar, |
| FSNS(XML_w, XML_val), "gregorian"); |
| m_pSerializer->endElementNS(XML_w, XML_date); |
| |
| if (aGrabBagSdt.hasElements()) |
| { |
| // There are some extra sdt parameters came from grab bag |
| SdtBlockHelper aSdtBlock; |
| aSdtBlock.GetSdtParamsFromGrabBag(aGrabBagSdt); |
| aSdtBlock.WriteExtraParams(m_pSerializer); |
| } |
| |
| m_pSerializer->endElementNS(XML_w, XML_sdtPr); |
| |
| m_pSerializer->startElementNS(XML_w, XML_sdtContent); |
| } |
| |
| void DocxAttributeOutput::WriteSdtPlainText(const OUString & sValue, const uno::Sequence<beans::PropertyValue>& aGrabBagSdt) |
| { |
| m_pSerializer->startElementNS(XML_w, XML_sdt); |
| m_pSerializer->startElementNS(XML_w, XML_sdtPr); |
| |
| if (aGrabBagSdt.hasElements()) |
| { |
| // There are some extra sdt parameters came from grab bag |
| SdtBlockHelper aSdtBlock; |
| aSdtBlock.GetSdtParamsFromGrabBag(aGrabBagSdt); |
| aSdtBlock.WriteExtraParams(m_pSerializer); |
| |
| if (aSdtBlock.m_nSdtPrToken && aSdtBlock.m_nSdtPrToken != FSNS(XML_w, XML_id)) |
| { |
| // Write <w:text/> or whatsoever from grabbag |
| m_pSerializer->singleElement(aSdtBlock.m_nSdtPrToken); |
| } |
| |
| // Store databindings data for later writing to corresponding XMLs |
| OUString sPrefixMapping, sXpath; |
| for (const auto& rProp : aGrabBagSdt) |
| { |
| if (rProp.Name == "ooxml:CT_SdtPr_dataBinding") |
| { |
| uno::Sequence<beans::PropertyValue> aDataBindingProps; |
| rProp.Value >>= aDataBindingProps; |
| for (const auto& rDBProp : aDataBindingProps) |
| { |
| if (rDBProp.Name == "ooxml:CT_DataBinding_prefixMappings") |
| sPrefixMapping = rDBProp.Value.get<OUString>(); |
| else if (rDBProp.Name == "ooxml:CT_DataBinding_xpath") |
| sXpath = rDBProp.Value.get<OUString>(); |
| } |
| } |
| } |
| |
| if (sXpath.getLength()) |
| { |
| // Given xpath is sufficient |
| m_rExport.AddSdtData(sPrefixMapping, sXpath, sValue); |
| } |
| } |
| |
| m_pSerializer->endElementNS(XML_w, XML_sdtPr); |
| |
| m_pSerializer->startElementNS(XML_w, XML_sdtContent); |
| } |
| |
| void DocxAttributeOutput::WriteContentControlStart() |
| { |
| if (!m_pContentControl) |
| { |
| return; |
| } |
| |
| m_pSerializer->startElementNS(XML_w, XML_sdt); |
| m_pSerializer->startElementNS(XML_w, XML_sdtPr); |
| if (!m_pContentControl->GetPlaceholderDocPart().isEmpty()) |
| { |
| m_pSerializer->startElementNS(XML_w, XML_placeholder); |
| m_pSerializer->singleElementNS(XML_w, XML_docPart, FSNS(XML_w, XML_val), |
| m_pContentControl->GetPlaceholderDocPart()); |
| m_pSerializer->endElementNS(XML_w, XML_placeholder); |
| } |
| |
| if (!m_pContentControl->GetDataBindingPrefixMappings().isEmpty() || !m_pContentControl->GetDataBindingXpath().isEmpty() || !m_pContentControl->GetDataBindingStoreItemID().isEmpty()) |
| { |
| m_pSerializer->singleElementNS( XML_w, XML_dataBinding, |
| FSNS(XML_w, XML_prefixMappings), m_pContentControl->GetDataBindingPrefixMappings(), |
| FSNS(XML_w, XML_xpath), m_pContentControl->GetDataBindingXpath(), |
| FSNS(XML_w, XML_storeItemID), m_pContentControl->GetDataBindingStoreItemID()); |
| } |
| |
| if (!m_pContentControl->GetColor().isEmpty()) |
| { |
| m_pSerializer->singleElementNS(XML_w15, XML_color, FSNS(XML_w, XML_val), |
| m_pContentControl->GetColor()); |
| } |
| |
| if (!m_pContentControl->GetAppearance().isEmpty()) |
| { |
| m_pSerializer->singleElementNS(XML_w15, XML_appearance, FSNS(XML_w15, XML_val), |
| m_pContentControl->GetAppearance()); |
| } |
| |
| if (!m_pContentControl->GetAlias().isEmpty()) |
| { |
| m_pSerializer->singleElementNS(XML_w, XML_alias, FSNS(XML_w, XML_val), |
| m_pContentControl->GetAlias()); |
| } |
| |
| if (!m_pContentControl->GetTag().isEmpty()) |
| { |
| m_pSerializer->singleElementNS(XML_w, XML_tag, FSNS(XML_w, XML_val), |
| m_pContentControl->GetTag()); |
| } |
| |
| if (m_pContentControl->GetId()) |
| { |
| m_pSerializer->singleElementNS(XML_w, XML_id, FSNS(XML_w, XML_val), |
| OString::number(m_pContentControl->GetId())); |
| } |
| |
| if (m_pContentControl->GetTabIndex()) |
| { |
| // write the unsigned value as if it were signed since that is all we can import |
| const sal_Int32 nTabIndex = static_cast<sal_Int32>(m_pContentControl->GetTabIndex()); |
| m_pSerializer->singleElementNS(XML_w, XML_tabIndex, FSNS(XML_w, XML_val), |
| OString::number(nTabIndex)); |
| } |
| |
| if (!m_pContentControl->GetLock().isEmpty()) |
| { |
| m_pSerializer->singleElementNS(XML_w, XML_lock, FSNS(XML_w, XML_val), |
| m_pContentControl->GetLock()); |
| } |
| |
| if (m_pContentControl->GetShowingPlaceHolder()) |
| { |
| m_pSerializer->singleElementNS(XML_w, XML_showingPlcHdr); |
| } |
| |
| if (m_pContentControl->GetPicture()) |
| { |
| m_pSerializer->singleElementNS(XML_w, XML_picture); |
| } |
| |
| if (m_pContentControl->GetCheckbox()) |
| { |
| m_pSerializer->startElementNS(XML_w14, XML_checkbox); |
| m_pSerializer->singleElementNS(XML_w14, XML_checked, FSNS(XML_w14, XML_val), |
| OString::number(int(m_pContentControl->GetChecked()))); |
| OUString aCheckedState = m_pContentControl->GetCheckedState(); |
| if (!aCheckedState.isEmpty()) |
| { |
| m_pSerializer->singleElementNS(XML_w14, XML_checkedState, FSNS(XML_w14, XML_val), |
| OString::number(aCheckedState[0], /*radix=*/16)); |
| } |
| OUString aUncheckedState = m_pContentControl->GetUncheckedState(); |
| if (!aUncheckedState.isEmpty()) |
| { |
| m_pSerializer->singleElementNS(XML_w14, XML_uncheckedState, FSNS(XML_w14, XML_val), |
| OString::number(aUncheckedState[0], /*radix=*/16)); |
| } |
| m_pSerializer->endElementNS(XML_w14, XML_checkbox); |
| } |
| |
| if (m_pContentControl->GetComboBox() || m_pContentControl->GetDropDown()) |
| { |
| if (m_pContentControl->GetComboBox()) |
| { |
| m_pSerializer->startElementNS(XML_w, XML_comboBox); |
| } |
| else |
| { |
| m_pSerializer->startElementNS(XML_w, XML_dropDownList); |
| } |
| for (const auto& rItem : m_pContentControl->GetListItems()) |
| { |
| rtl::Reference<FastAttributeList> xAttributes = FastSerializerHelper::createAttrList(); |
| if (!rItem.m_aDisplayText.isEmpty()) |
| { |
| // If there is no display text, need to omit the attribute, not write an empty one. |
| xAttributes->add(FSNS(XML_w, XML_displayText), rItem.m_aDisplayText); |
| } |
| xAttributes->add(FSNS(XML_w, XML_value), rItem.m_aValue); |
| m_pSerializer->singleElementNS(XML_w, XML_listItem, xAttributes); |
| } |
| if (m_pContentControl->GetComboBox()) |
| { |
| m_pSerializer->endElementNS(XML_w, XML_comboBox); |
| } |
| else |
| { |
| m_pSerializer->endElementNS(XML_w, XML_dropDownList); |
| } |
| } |
| |
| if (m_pContentControl->GetDate()) |
| { |
| OUString aCurrentDate = m_pContentControl->GetCurrentDate(); |
| if (aCurrentDate.isEmpty()) |
| { |
| m_pSerializer->startElementNS(XML_w, XML_date); |
| } |
| else |
| { |
| m_pSerializer->startElementNS(XML_w, XML_date, FSNS(XML_w, XML_fullDate), aCurrentDate); |
| } |
| OUString aDateFormat = m_pContentControl->GetDateFormat().replaceAll("\"", "'"); |
| if (!aDateFormat.isEmpty()) |
| { |
| m_pSerializer->singleElementNS(XML_w, XML_dateFormat, FSNS(XML_w, XML_val), |
| aDateFormat); |
| } |
| OUString aDateLanguage = m_pContentControl->GetDateLanguage(); |
| if (!aDateLanguage.isEmpty()) |
| { |
| m_pSerializer->singleElementNS(XML_w, XML_lid, FSNS(XML_w, XML_val), |
| aDateLanguage); |
| } |
| m_pSerializer->endElementNS(XML_w, XML_date); |
| } |
| |
| if (!m_pContentControl->GetMultiLine().isEmpty()) |
| { |
| m_pSerializer->singleElementNS(XML_w, XML_text, FSNS(XML_w, XML_multiLine), m_pContentControl->GetMultiLine()); |
| } |
| else if (m_pContentControl->GetPlainText()) |
| { |
| m_pSerializer->singleElementNS(XML_w, XML_text); |
| } |
| |
| m_pSerializer->endElementNS(XML_w, XML_sdtPr); |
| m_pSerializer->startElementNS(XML_w, XML_sdtContent); |
| |
| const OUString& rPrefixMapping = m_pContentControl->GetDataBindingPrefixMappings(); |
| const OUString& rXpath = m_pContentControl->GetDataBindingXpath(); |
| if (!rXpath.isEmpty()) |
| { |
| // This content control has a data binding, update the data source. |
| SwTextContentControl* pTextAttr = m_pContentControl->GetTextAttr(); |
| SwTextNode* pTextNode = m_pContentControl->GetTextNode(); |
| if (pTextNode && pTextAttr) |
| { |
| SwPosition aPoint(*pTextNode, pTextAttr->GetStart()); |
| SwPosition aMark(*pTextNode, *pTextAttr->GetEnd()); |
| SwPaM aPam(aMark, aPoint); |
| OUString aSnippet = aPam.GetText(); |
| static sal_Unicode const aForbidden[] = { |
| CH_TXTATR_BREAKWORD, |
| 0 |
| }; |
| aSnippet = comphelper::string::removeAny(aSnippet, aForbidden); |
| m_rExport.AddSdtData(rPrefixMapping, rXpath, aSnippet); |
| } |
| } |
| |
| m_pContentControl = nullptr; |
| } |
| |
| void DocxAttributeOutput::WriteContentControlEnd() |
| { |
| m_pSerializer->endElementNS(XML_w, XML_sdtContent); |
| m_pSerializer->endElementNS(XML_w, XML_sdt); |
| } |
| |
| void DocxAttributeOutput::WriteSdtDropDownStart( |
| OUString const& rName, |
| OUString const& rSelected, |
| uno::Sequence<OUString> const& rListItems) |
| { |
| m_pSerializer->startElementNS(XML_w, XML_sdt); |
| m_pSerializer->startElementNS(XML_w, XML_sdtPr); |
| |
| m_pSerializer->singleElementNS(XML_w, XML_alias, |
| FSNS(XML_w, XML_val), rName); |
| |
| sal_Int32 nId = comphelper::findValue(rListItems, rSelected); |
| if (nId == -1) |
| { |
| nId = 0; |
| } |
| |
| m_pSerializer->startElementNS(XML_w, XML_dropDownList, |
| FSNS(XML_w, XML_lastValue), OString::number(nId)); |
| |
| for (auto const& rItem : rListItems) |
| { |
| auto const item(OUStringToOString(rItem, RTL_TEXTENCODING_UTF8)); |
| m_pSerializer->singleElementNS(XML_w, XML_listItem, |
| FSNS(XML_w, XML_value), item, |
| FSNS(XML_w, XML_displayText), item); |
| } |
| |
| m_pSerializer->endElementNS(XML_w, XML_dropDownList); |
| m_pSerializer->endElementNS(XML_w, XML_sdtPr); |
| |
| m_pSerializer->startElementNS(XML_w, XML_sdtContent); |
| } |
| |
| void DocxAttributeOutput::WriteSdtDropDownEnd(OUString const& rSelected, |
| uno::Sequence<OUString> const& rListItems) |
| { |
| // note: rSelected might be empty? |
| sal_Int32 nId = comphelper::findValue(rListItems, rSelected); |
| if (nId == -1) |
| { |
| nId = 0; |
| } |
| |
| // the lastValue only identifies the entry in the list, also export |
| // currently selected item's displayText as run content (if one exists) |
| if (rListItems.size()) |
| { |
| m_pSerializer->startElementNS(XML_w, XML_r); |
| m_pSerializer->startElementNS(XML_w, XML_t); |
| m_pSerializer->writeEscaped(rListItems[nId]); |
| m_pSerializer->endElementNS(XML_w, XML_t); |
| m_pSerializer->endElementNS(XML_w, XML_r); |
| } |
| |
| WriteContentControlEnd(); |
| } |
| |
| void DocxAttributeOutput::StartField_Impl( const SwTextNode* pNode, sal_Int32 nPos, FieldInfos const & rInfos, bool bWriteRun ) |
| { |
| if ( rInfos.pField && rInfos.eType == ww::eUNKNOWN ) |
| { |
| // Expand unsupported fields |
| RunText(rInfos.pField->ExpandField(/*bCached=*/true, nullptr)); |
| return; |
| } |
| else if ( rInfos.eType == ww::eFORMDATE ) |
| { |
| const sw::mark::IDateFieldmark& rFieldmark = dynamic_cast<const sw::mark::IDateFieldmark&>(*rInfos.pFieldmark); |
| FieldMarkParamsHelper params(rFieldmark); |
| |
| OUString sFullDate; |
| OUString sCurrentDate; |
| params.extractParam( ODF_FORMDATE_CURRENTDATE, sCurrentDate ); |
| if(!sCurrentDate.isEmpty()) |
| { |
| sFullDate = sCurrentDate + "T00:00:00Z"; |
| } |
| else |
| { |
| std::pair<bool, double> aResult = rFieldmark.GetCurrentDate(); |
| if(aResult.first) |
| { |
| sFullDate = rFieldmark.GetDateInStandardDateFormat(aResult.second) + "T00:00:00Z"; |
| } |
| } |
| |
| OUString sDateFormat; |
| params.extractParam( ODF_FORMDATE_DATEFORMAT, sDateFormat ); |
| OUString sLang; |
| params.extractParam( ODF_FORMDATE_DATEFORMAT_LANGUAGE, sLang ); |
| |
| uno::Sequence<beans::PropertyValue> aSdtParams; |
| params.extractParam(UNO_NAME_MISC_OBJ_INTEROPGRABBAG, aSdtParams); |
| |
| WriteFormDateStart( sFullDate, sDateFormat, sLang, aSdtParams); |
| return; |
| } |
| else if (rInfos.eType == ww::eFORMDROPDOWN && rInfos.pField) |
| { |
| assert(!rInfos.pFieldmark); |
| SwDropDownField const& rField2(*static_cast<SwDropDownField const*>(rInfos.pField.get())); |
| WriteSdtDropDownStart(rField2.GetName(), |
| rField2.GetSelectedItem(), |
| rField2.GetItemSequence()); |
| return; |
| } |
| else if (rInfos.eType == ww::eFILLIN) |
| { |
| const SwInputField* pField = static_cast<SwInputField const*>(rInfos.pField.get()); |
| if (pField && pField->getGrabBagParams().hasElements()) |
| { |
| WriteSdtPlainText(pField->GetPar1(), pField->getGrabBagParams()); |
| m_sRawText = pField->GetPar1(); // Write field content also as a fallback |
| return; |
| } |
| } |
| |
| if ( rInfos.eType != ww::eNONE ) // HYPERLINK fields are just commands |
| { |
| if ( bWriteRun ) |
| m_pSerializer->startElementNS(XML_w, XML_r); |
| |
| if ( rInfos.eType == ww::eFORMDROPDOWN ) |
| { |
| m_pSerializer->startElementNS( XML_w, XML_fldChar, |
| FSNS( XML_w, XML_fldCharType ), "begin" ); |
| assert( rInfos.pFieldmark && !rInfos.pField ); |
| WriteFFData(rInfos); |
| m_pSerializer->endElementNS( XML_w, XML_fldChar ); |
| |
| if ( bWriteRun ) |
| m_pSerializer->endElementNS( XML_w, XML_r ); |
| |
| CmdField_Impl( pNode, nPos, rInfos, bWriteRun ); |
| } |
| else |
| { |
| // Write the field start |
| if ( rInfos.pField && (rInfos.pField->Which() == SwFieldIds::DateTime) && rInfos.pField->GetSubType() & FIXEDFLD ) |
| { |
| m_pSerializer->startElementNS( XML_w, XML_fldChar, |
| FSNS( XML_w, XML_fldCharType ), "begin", |
| FSNS( XML_w, XML_fldLock ), "true" ); |
| } |
| else |
| { |
| m_pSerializer->startElementNS( XML_w, XML_fldChar, |
| FSNS( XML_w, XML_fldCharType ), "begin" ); |
| } |
| |
| if ( rInfos.pFieldmark ) |
| WriteFFData( rInfos ); |
| |
| m_pSerializer->endElementNS( XML_w, XML_fldChar ); |
| |
| if ( bWriteRun ) |
| m_pSerializer->endElementNS( XML_w, XML_r ); |
| |
| // The hyperlinks fields can't be expanded: the value is |
| // normally in the text run |
| if ( !rInfos.pField ) |
| CmdField_Impl( pNode, nPos, rInfos, bWriteRun ); |
| else |
| m_bWritingField = true; |
| } |
| } |
| } |
| |
| void DocxAttributeOutput::DoWriteCmd( std::u16string_view rCmd ) |
| { |
| std::u16string_view sCmd = o3tl::trim(rCmd); |
| if (o3tl::starts_with(sCmd, u"SEQ")) |
| { |
| OUString sSeqName( o3tl::trim(msfilter::util::findQuotedText(sCmd, u"SEQ ", '\\')) ); |
| m_aSeqBookmarksNames[sSeqName].push_back(m_sLastOpenedBookmark); |
| } |
| // Write the Field command |
| sal_Int32 nTextToken = XML_instrText; |
| if ( m_pRedlineData && m_pRedlineData->GetType() == RedlineType::Delete ) |
| nTextToken = XML_delInstrText; |
| |
| m_pSerializer->startElementNS(XML_w, nTextToken, FSNS(XML_xml, XML_space), "preserve"); |
| m_pSerializer->writeEscaped( rCmd ); |
| m_pSerializer->endElementNS( XML_w, nTextToken ); |
| |
| } |
| |
| void DocxAttributeOutput::CmdField_Impl( const SwTextNode* pNode, sal_Int32 nPos, FieldInfos const & rInfos, bool bWriteRun ) |
| { |
| // Write the Field instruction |
| if ( bWriteRun ) |
| { |
| bool bWriteCombChars(false); |
| m_pSerializer->startElementNS(XML_w, XML_r); |
| |
| if (rInfos.eType == ww::eEQ) |
| bWriteCombChars = true; |
| |
| DoWriteFieldRunProperties( pNode, nPos, bWriteCombChars ); |
| } |
| |
| sal_Int32 nIdx { rInfos.sCmd.isEmpty() ? -1 : 0 }; |
| while ( nIdx >= 0 ) |
| { |
| OUString sToken = rInfos.sCmd.getToken( 0, '\t', nIdx ); |
| if ( rInfos.eType == ww::eCREATEDATE |
| || rInfos.eType == ww::eSAVEDATE |
| || rInfos.eType == ww::ePRINTDATE |
| || rInfos.eType == ww::eDATE |
| || rInfos.eType == ww::eTIME ) |
| { |
| sToken = sToken.replaceAll("NNNN", "dddd"); |
| sToken = sToken.replaceAll("NN", "ddd"); |
| } |
| else if ( rInfos.eType == ww::eEquals ) |
| { |
| // Use original OOXML formula, if it exists and its conversion hasn't been changed |
| bool bIsChanged = true; |
| if ( pNode->GetTableBox() ) |
| { |
| if ( const SfxGrabBagItem* pItem = pNode->GetTableBox()->GetFrameFormat()->GetAttrSet().GetItem<SfxGrabBagItem>(RES_FRMATR_GRABBAG) ) |
| { |
| OUString sActualFormula = sToken.trim(); |
| const std::map<OUString, uno::Any>& rGrabBag = pItem->GetGrabBag(); |
| std::map<OUString, uno::Any>::const_iterator aStoredFormula = rGrabBag.find(u"CellFormulaConverted"_ustr); |
| if ( aStoredFormula != rGrabBag.end() && sActualFormula.indexOf('=') == 0 && |
| o3tl::trim(sActualFormula.subView(1)) == o3tl::trim(aStoredFormula->second.get<OUString>()) ) |
| { |
| aStoredFormula = rGrabBag.find(u"CellFormula"_ustr); |
| if ( aStoredFormula != rGrabBag.end() ) |
| { |
| sToken = " =" + aStoredFormula->second.get<OUString>(); |
| bIsChanged = false; |
| } |
| } |
| } |
| } |
| |
| if ( bIsChanged ) |
| { |
| UErrorCode nErr(U_ZERO_ERROR); |
| icu::UnicodeString sInput(sToken.getStr()); |
| // remove < and > around cell references, e.g. <A1> to A1, <A1:B2> to A1:B2 |
| icu::RegexMatcher aMatcher("<([A-Z]{1,3}[0-9]+(:[A-Z]{1,3}[0-9]+)?)>", sInput, 0, nErr); |
| sInput = aMatcher.replaceAll(icu::UnicodeString("$1"), nErr); |
| // convert MEAN to AVERAGE |
| icu::RegexMatcher aMatcher2("\\bMEAN\\b", sInput, UREGEX_CASE_INSENSITIVE, nErr); |
| sToken = aMatcher2.replaceAll(icu::UnicodeString("AVERAGE"), nErr).getTerminatedBuffer(); |
| } |
| } |
| |
| // Write the Field command |
| DoWriteCmd( sToken ); |
| |
| // Replace tabs by </instrText><tab/><instrText> |
| if ( nIdx > 0 ) // Is another token expected? |
| RunText( u"\t"_ustr ); |
| } |
| |
| if ( bWriteRun ) |
| { |
| m_pSerializer->endElementNS( XML_w, XML_r ); |
| } |
| } |
| |
| void DocxAttributeOutput::CmdEndField_Impl(SwTextNode const*const pNode, |
| sal_Int32 const nPos, bool const bWriteRun) |
| { |
| // Write the Field separator |
| if ( bWriteRun ) |
| { |
| m_pSerializer->startElementNS(XML_w, XML_r); |
| DoWriteFieldRunProperties( pNode, nPos ); |
| } |
| |
| m_pSerializer->singleElementNS( XML_w, XML_fldChar, |
| FSNS( XML_w, XML_fldCharType ), "separate" ); |
| |
| if ( bWriteRun ) |
| { |
| m_pSerializer->endElementNS( XML_w, XML_r ); |
| } |
| } |
| |
| /// Writes properties for run that is used to separate field implementation. |
| /// There are several runs are used: |
| /// <w:r> |
| /// <w:rPr> |
| /// <!-- properties written with StartRunProperties() / EndRunProperties(). |
| /// </w:rPr> |
| /// <w:fldChar w:fldCharType="begin" /> |
| /// </w:r> |
| /// <w:r> |
| /// <w:rPr> |
| /// <!-- properties written with DoWriteFieldRunProperties() |
| /// </w:rPr> |
| /// <w:instrText>TIME \@"HH:mm:ss"</w:instrText> |
| /// </w:r> |
| /// <w:r> |
| /// <w:rPr> |
| /// <!-- properties written with DoWriteFieldRunProperties() |
| /// </w:rPr> |
| /// <w:fldChar w:fldCharType="separate" /> |
| /// </w:r> |
| /// <w:r> |
| /// <w:rPr> |
| /// <!-- properties written with DoWriteFieldRunProperties() |
| /// </w:rPr> |
| /// <w:t>14:01:13</w:t> |
| /// </w:r> |
| /// <w:r> |
| /// <w:rPr> |
| /// <!-- properties written with DoWriteFieldRunProperties() |
| /// </w:rPr> |
| /// <w:fldChar w:fldCharType="end" /> |
| /// </w:r> |
| /// See, tdf#38778 |
| void DocxAttributeOutput::DoWriteFieldRunProperties( const SwTextNode * pNode, sal_Int32 nPos, bool bWriteCombChars) |
| { |
| if (! pNode) |
| { |
| // nothing to do |
| return; |
| } |
| |
| m_bPreventDoubleFieldsHandling = true; |
| |
| { |
| m_pSerializer->startElementNS(XML_w, XML_rPr); |
| |
| // 1. output webHidden flag |
| if(GetExport().m_bHideTabLeaderAndPageNumbers && m_pHyperlinkAttrList.is() ) |
| { |
| m_pSerializer->singleElementNS(XML_w, XML_webHidden); |
| } |
| |
| // 2. find all active character properties |
| SwWW8AttrIter aAttrIt( m_rExport, *pNode ); |
| aAttrIt.OutAttr( nPos, bWriteCombChars ); |
| |
| // 3. write the character properties |
| WriteCollectedRunProperties(); |
| |
| m_pSerializer->endElementNS( XML_w, XML_rPr ); |
| } |
| |
| m_bPreventDoubleFieldsHandling = false; |
| } |
| |
| void DocxAttributeOutput::EndField_Impl( const SwTextNode* pNode, sal_Int32 nPos, FieldInfos& rInfos ) |
| { |
| if (rInfos.eType == ww::eFORMDATE) |
| { |
| WriteContentControlEnd(); |
| return; |
| } |
| else if (rInfos.eType == ww::eFORMDROPDOWN && rInfos.pField) |
| { |
| // write selected item from End not Start to ensure that any bookmarks |
| // precede it |
| SwDropDownField const& rField(*static_cast<SwDropDownField const*>(rInfos.pField.get())); |
| WriteSdtDropDownEnd(rField.GetSelectedItem(), rField.GetItemSequence()); |
| return; |
| } |
| else if (rInfos.eType == ww::eFILLIN && rInfos.pField) |
| { |
| SwInputField const& rField(*static_cast<SwInputField const*>(rInfos.pField.get())); |
| if (rField.getGrabBagParams().hasElements()) |
| { |
| WriteContentControlEnd(); |
| return; |
| } |
| } |
| // The command has to be written before for the hyperlinks |
| if ( rInfos.pField ) |
| { |
| CmdField_Impl( pNode, nPos, rInfos, true ); |
| CmdEndField_Impl( pNode, nPos, true ); |
| } |
| |
| // Write the bookmark start if any |
| if ( !m_sFieldBkm.isEmpty() ) |
| { |
| DoWriteBookmarkTagStart(m_sFieldBkm); |
| } |
| |
| if (rInfos.pField ) // For hyperlinks and TOX |
| { |
| // Write the Field latest value |
| m_pSerializer->startElementNS(XML_w, XML_r); |
| DoWriteFieldRunProperties( pNode, nPos ); |
| |
| OUString sExpand; |
| if(rInfos.eType == ww::eCITATION) |
| { |
| sExpand = static_cast<SwAuthorityField const*>(rInfos.pField.get()) |
| ->ExpandCitation(AUTH_FIELD_TITLE, nullptr); |
| } |
| else if(rInfos.eType != ww::eFORMDROPDOWN) |
| { |
| sExpand = rInfos.pField->ExpandField(true, nullptr); |
| } |
| // newlines embedded in fields are 0x0B in MSO and 0x0A for us |
| RunText(sExpand.replace(0x0A, 0x0B)); |
| |
| m_pSerializer->endElementNS( XML_w, XML_r ); |
| } |
| |
| // Write the bookmark end if any |
| if ( !m_sFieldBkm.isEmpty() ) |
| { |
| DoWriteBookmarkTagEnd(m_nNextBookmarkId); |
| |
| m_nNextBookmarkId++; |
| } |
| |
| // Write the Field end |
| if ( rInfos.bClose ) |
| { |
| m_bWritingField = false; |
| m_pSerializer->startElementNS(XML_w, XML_r); |
| DoWriteFieldRunProperties( pNode, nPos ); |
| m_pSerializer->singleElementNS(XML_w, XML_fldChar, FSNS(XML_w, XML_fldCharType), "end"); |
| m_pSerializer->endElementNS( XML_w, XML_r ); |
| } |
| // Write the ref field if a bookmark had to be set and the field |
| // should be visible |
| if ( !rInfos.pField ) |
| { |
| m_sFieldBkm.clear(); |
| return; |
| } |
| |
| sal_uInt16 nSubType = rInfos.pField->GetSubType( ); |
| bool bIsSetField = rInfos.pField->GetTyp( )->Which( ) == SwFieldIds::SetExp; |
| bool bShowRef = bIsSetField && ( nSubType & nsSwExtendedSubType::SUB_INVISIBLE ) == 0; |
| |
| if (!bShowRef) |
| { |
| m_sFieldBkm.clear(); |
| } |
| |
| if (m_sFieldBkm.isEmpty()) |
| return; |
| |
| // Write the field beginning |
| m_pSerializer->startElementNS(XML_w, XML_r); |
| m_pSerializer->singleElementNS( XML_w, XML_fldChar, |
| FSNS( XML_w, XML_fldCharType ), "begin" ); |
| m_pSerializer->endElementNS( XML_w, XML_r ); |
| |
| rInfos.sCmd = FieldString( ww::eREF ); |
| rInfos.sCmd += "\""; |
| rInfos.sCmd += m_sFieldBkm; |
| rInfos.sCmd += "\" "; |
| |
| // Clean the field bookmark data to avoid infinite loop |
| m_sFieldBkm = OUString( ); |
| |
| // Write the end of the field |
| EndField_Impl( pNode, nPos, rInfos ); |
| } |
| |
| void DocxAttributeOutput::StartRunProperties() |
| { |
| // postpone the output so that we can later [in EndRunProperties()] |
| // prepend the properties before the text |
| m_pSerializer->mark(Tag_StartRunProperties); |
| |
| m_pSerializer->startElementNS(XML_w, XML_rPr); |
| |
| if(GetExport().m_bHideTabLeaderAndPageNumbers && m_pHyperlinkAttrList.is() ) |
| { |
| m_pSerializer->singleElementNS(XML_w, XML_webHidden); |
| } |
| InitCollectedRunProperties(); |
| |
| assert( !m_oPostponedGraphic ); |
| m_oPostponedGraphic.emplace(); |
| |
| assert( !m_oPostponedDiagrams ); |
| m_oPostponedDiagrams.emplace(); |
| |
| assert(!m_oPostponedDMLDrawings); |
| m_oPostponedDMLDrawings.emplace(); |
| |
| assert( !m_oPostponedOLEs ); |
| m_oPostponedOLEs.emplace(); |
| } |
| |
| void DocxAttributeOutput::InitCollectedRunProperties() |
| { |
| m_pFontsAttrList = nullptr; |
| m_pEastAsianLayoutAttrList = nullptr; |
| m_pCharLangAttrList = nullptr; |
| |
| // Write the elements in the spec order |
| static const sal_Int32 aOrder[] = |
| { |
| FSNS( XML_w, XML_rStyle ), |
| FSNS( XML_w, XML_rFonts ), |
| FSNS( XML_w, XML_b ), |
| FSNS( XML_w, XML_bCs ), |
| FSNS( XML_w, XML_i ), |
| FSNS( XML_w, XML_iCs ), |
| FSNS( XML_w, XML_caps ), |
| FSNS( XML_w, XML_smallCaps ), |
| FSNS( XML_w, XML_strike ), |
| FSNS( XML_w, XML_dstrike ), |
| FSNS( XML_w, XML_outline ), |
| FSNS( XML_w, XML_shadow ), |
| FSNS( XML_w, XML_emboss ), |
| FSNS( XML_w, XML_imprint ), |
| FSNS( XML_w, XML_noProof ), |
| FSNS( XML_w, XML_snapToGrid ), |
| FSNS( XML_w, XML_vanish ), |
| FSNS( XML_w, XML_webHidden ), |
| FSNS( XML_w, XML_color ), |
| FSNS( XML_w, XML_spacing ), |
| FSNS( XML_w, XML_w ), |
| FSNS( XML_w, XML_kern ), |
| FSNS( XML_w, XML_position ), |
| FSNS( XML_w, XML_sz ), |
| FSNS( XML_w, XML_szCs ), |
| FSNS( XML_w, XML_highlight ), |
| FSNS( XML_w, XML_u ), |
| FSNS( XML_w, XML_effect ), |
| FSNS( XML_w, XML_bdr ), |
| FSNS( XML_w, XML_shd ), |
| FSNS( XML_w, XML_fitText ), |
| FSNS( XML_w, XML_vertAlign ), |
| FSNS( XML_w, XML_rtl ), |
| FSNS( XML_w, XML_cs ), |
| FSNS( XML_w, XML_em ), |
| FSNS( XML_w, XML_lang ), |
| FSNS( XML_w, XML_eastAsianLayout ), |
| FSNS( XML_w, XML_specVanish ), |
| FSNS( XML_w, XML_oMath ), |
| FSNS( XML_w, XML_rPrChange ), |
| FSNS( XML_w, XML_del ), |
| FSNS( XML_w, XML_ins ), |
| FSNS( XML_w, XML_moveFrom ), |
| FSNS( XML_w, XML_moveTo ), |
| FSNS( XML_w14, XML_glow ), |
| FSNS( XML_w14, XML_shadow ), |
| FSNS( XML_w14, XML_reflection ), |
| FSNS( XML_w14, XML_textOutline ), |
| FSNS( XML_w14, XML_textFill ), |
| FSNS( XML_w14, XML_scene3d ), |
| FSNS( XML_w14, XML_props3d ), |
| FSNS( XML_w14, XML_ligatures ), |
| FSNS( XML_w14, XML_numForm ), |
| FSNS( XML_w14, XML_numSpacing ), |
| FSNS( XML_w14, XML_stylisticSets ), |
| FSNS( XML_w14, XML_cntxtAlts ), |
| }; |
| |
| // postpone the output so that we can later [in EndParagraphProperties()] |
| // prepend the properties before the run |
| // coverity[overrun-buffer-arg : FALSE] - coverity has difficulty with css::uno::Sequence |
| m_pSerializer->mark(Tag_InitCollectedRunProperties, comphelper::containerToSequence(aOrder)); |
| } |
| |
| namespace |
| { |
| |
| struct NameToId |
| { |
| OUString maName; |
| sal_Int32 maId; |
| }; |
| |
| const NameToId constNameToIdMapping[] = |
| { |
| { u"glow"_ustr, FSNS( XML_w14, XML_glow ) }, |
| { u"shadow"_ustr, FSNS( XML_w14, XML_shadow ) }, |
| { u"reflection"_ustr, FSNS( XML_w14, XML_reflection ) }, |
| { u"textOutline"_ustr, FSNS( XML_w14, XML_textOutline ) }, |
| { u"textFill"_ustr, FSNS( XML_w14, XML_textFill ) }, |
| { u"scene3d"_ustr, FSNS( XML_w14, XML_scene3d ) }, |
| { u"props3d"_ustr, FSNS( XML_w14, XML_props3d ) }, |
| { u"ligatures"_ustr, FSNS( XML_w14, XML_ligatures ) }, |
| { u"numForm"_ustr, FSNS( XML_w14, XML_numForm ) }, |
| { u"numSpacing"_ustr, FSNS( XML_w14, XML_numSpacing ) }, |
| { u"stylisticSets"_ustr,FSNS( XML_w14, XML_stylisticSets ) }, |
| { u"cntxtAlts"_ustr, FSNS( XML_w14, XML_cntxtAlts ) }, |
| |
| { u"val"_ustr, FSNS( XML_w14, XML_val ) }, |
| { u"rad"_ustr, FSNS( XML_w14, XML_rad ) }, |
| { u"blurRad"_ustr, FSNS( XML_w14, XML_blurRad ) }, |
| { u"stA"_ustr, FSNS( XML_w14, XML_stA ) }, |
| { u"stPos"_ustr, FSNS( XML_w14, XML_stPos ) }, |
| { u"endA"_ustr, FSNS( XML_w14, XML_endA ) }, |
| { u"endPos"_ustr, FSNS( XML_w14, XML_endPos ) }, |
| { u"dist"_ustr, FSNS( XML_w14, XML_dist ) }, |
| { u"dir"_ustr, FSNS( XML_w14, XML_dir ) }, |
| { u"fadeDir"_ustr, FSNS( XML_w14, XML_fadeDir ) }, |
| { u"sx"_ustr, FSNS( XML_w14, XML_sx ) }, |
| { u"sy"_ustr, FSNS( XML_w14, XML_sy ) }, |
| { u"kx"_ustr, FSNS( XML_w14, XML_kx ) }, |
| { u"ky"_ustr, FSNS( XML_w14, XML_ky ) }, |
| { u"algn"_ustr, FSNS( XML_w14, XML_algn ) }, |
| { u"w"_ustr, FSNS( XML_w14, XML_w ) }, |
| { u"cap"_ustr, FSNS( XML_w14, XML_cap ) }, |
| { u"cmpd"_ustr, FSNS( XML_w14, XML_cmpd ) }, |
| { u"pos"_ustr, FSNS( XML_w14, XML_pos ) }, |
| { u"ang"_ustr, FSNS( XML_w14, XML_ang ) }, |
| { u"scaled"_ustr, FSNS( XML_w14, XML_scaled ) }, |
| { u"path"_ustr, FSNS( XML_w14, XML_path ) }, |
| { u"l"_ustr, FSNS( XML_w14, XML_l ) }, |
| { u"t"_ustr, FSNS( XML_w14, XML_t ) }, |
| { u"r"_ustr, FSNS( XML_w14, XML_r ) }, |
| { u"b"_ustr, FSNS( XML_w14, XML_b ) }, |
| { u"lim"_ustr, FSNS( XML_w14, XML_lim ) }, |
| { u"prst"_ustr, FSNS( XML_w14, XML_prst ) }, |
| { u"rig"_ustr, FSNS( XML_w14, XML_rig ) }, |
| { u"lat"_ustr, FSNS( XML_w14, XML_lat ) }, |
| { u"lon"_ustr, FSNS( XML_w14, XML_lon ) }, |
| { u"rev"_ustr, FSNS( XML_w14, XML_rev ) }, |
| { u"h"_ustr, FSNS( XML_w14, XML_h ) }, |
| { u"extrusionH"_ustr, FSNS( XML_w14, XML_extrusionH ) }, |
| { u"contourW"_ustr, FSNS( XML_w14, XML_contourW ) }, |
| { u"prstMaterial"_ustr, FSNS( XML_w14, XML_prstMaterial ) }, |
| { u"id"_ustr, FSNS( XML_w14, XML_id ) }, |
| |
| { u"schemeClr"_ustr, FSNS( XML_w14, XML_schemeClr ) }, |
| { u"srgbClr"_ustr, FSNS( XML_w14, XML_srgbClr ) }, |
| { u"tint"_ustr, FSNS( XML_w14, XML_tint ) }, |
| { u"shade"_ustr, FSNS( XML_w14, XML_shade ) }, |
| { u"alpha"_ustr, FSNS( XML_w14, XML_alpha ) }, |
| { u"hueMod"_ustr, FSNS( XML_w14, XML_hueMod ) }, |
| { u"sat"_ustr, FSNS( XML_w14, XML_sat ) }, |
| { u"satOff"_ustr, FSNS( XML_w14, XML_satOff ) }, |
| { u"satMod"_ustr, FSNS( XML_w14, XML_satMod ) }, |
| { u"lum"_ustr, FSNS( XML_w14, XML_lum ) }, |
| { u"lumOff"_ustr, FSNS( XML_w14, XML_lumOff ) }, |
| { u"lumMod"_ustr, FSNS( XML_w14, XML_lumMod ) }, |
| { u"noFill"_ustr, FSNS( XML_w14, XML_noFill ) }, |
| { u"solidFill"_ustr, FSNS( XML_w14, XML_solidFill ) }, |
| { u"gradFill"_ustr, FSNS( XML_w14, XML_gradFill ) }, |
| { u"gsLst"_ustr, FSNS( XML_w14, XML_gsLst ) }, |
| { u"gs"_ustr, FSNS( XML_w14, XML_gs ) }, |
| { u"pos"_ustr, FSNS( XML_w14, XML_pos ) }, |
| { u"lin"_ustr, FSNS( XML_w14, XML_lin ) }, |
| { u"path"_ustr, FSNS( XML_w14, XML_path ) }, |
| { u"fillToRect"_ustr, FSNS( XML_w14, XML_fillToRect ) }, |
| { u"prstDash"_ustr, FSNS( XML_w14, XML_prstDash ) }, |
| { u"round"_ustr, FSNS( XML_w14, XML_round ) }, |
| { u"bevel"_ustr, FSNS( XML_w14, XML_bevel ) }, |
| { u"miter"_ustr, FSNS( XML_w14, XML_miter ) }, |
| { u"camera"_ustr, FSNS( XML_w14, XML_camera ) }, |
| { u"lightRig"_ustr, FSNS( XML_w14, XML_lightRig ) }, |
| { u"rot"_ustr, FSNS( XML_w14, XML_rot ) }, |
| { u"bevelT"_ustr, FSNS( XML_w14, XML_bevelT ) }, |
| { u"bevelB"_ustr, FSNS( XML_w14, XML_bevelB ) }, |
| { u"extrusionClr"_ustr, FSNS( XML_w14, XML_extrusionClr ) }, |
| { u"contourClr"_ustr, FSNS( XML_w14, XML_contourClr ) }, |
| { u"styleSet"_ustr, FSNS( XML_w14, XML_styleSet ) }, |
| }; |
| |
| std::optional<sal_Int32> lclGetElementIdForName(std::u16string_view rName) |
| { |
| for (auto const & i : constNameToIdMapping) |
| { |
| if (rName == i.maName) |
| { |
| return i.maId; |
| } |
| } |
| return std::optional<sal_Int32>(); |
| } |
| |
| void lclProcessRecursiveGrabBag(sal_Int32 aElementId, const css::uno::Sequence<css::beans::PropertyValue>& rElements, sax_fastparser::FSHelperPtr const & pSerializer) |
| { |
| css::uno::Sequence<css::beans::PropertyValue> aAttributes; |
| rtl::Reference<FastAttributeList> pAttributes = FastSerializerHelper::createAttrList(); |
| |
| for (const auto& rElement : rElements) |
| { |
| if (rElement.Name == "attributes") |
| { |
| rElement.Value >>= aAttributes; |
| } |
| } |
| |
| for (const auto& rAttribute : aAttributes) |
| { |
| uno::Any aAny = rAttribute.Value; |
| OString aValue; |
| |
| if(aAny.getValueType() == cppu::UnoType<sal_Int32>::get()) |
| { |
| aValue = OString::number(aAny.get<sal_Int32>()); |
| } |
| else if(aAny.getValueType() == cppu::UnoType<OUString>::get()) |
| { |
| aValue = OUStringToOString(aAny.get<OUString>(), RTL_TEXTENCODING_ASCII_US); |
| } |
| |
| std::optional<sal_Int32> aSubElementId = lclGetElementIdForName(rAttribute.Name); |
| if(aSubElementId) |
| pAttributes->add(*aSubElementId, aValue); |
| } |
| |
| pSerializer->startElement(aElementId, pAttributes); |
| |
| for (const auto& rElement : rElements) |
| { |
| css::uno::Sequence<css::beans::PropertyValue> aSumElements; |
| |
| std::optional<sal_Int32> aSubElementId = lclGetElementIdForName(rElement.Name); |
| if(aSubElementId) |
| { |
| rElement.Value >>= aSumElements; |
| lclProcessRecursiveGrabBag(*aSubElementId, aSumElements, pSerializer); |
| } |
| } |
| |
| pSerializer->endElement(aElementId); |
| } |
| |
| } |
| |
| void DocxAttributeOutput::WriteCollectedRunProperties() |
| { |
| // Write all differed properties |
| if ( m_pFontsAttrList.is() ) |
| { |
| m_pSerializer->singleElementNS( XML_w, XML_rFonts, detachFrom( m_pFontsAttrList ) ); |
| } |
| |
| if ( m_pColorAttrList.is() ) |
| { |
| m_pSerializer->singleElementNS( XML_w, XML_color, m_pColorAttrList ); |
| } |
| |
| if ( m_pEastAsianLayoutAttrList.is() ) |
| { |
| m_pSerializer->singleElementNS( XML_w, XML_eastAsianLayout, |
| detachFrom(m_pEastAsianLayoutAttrList ) ); |
| } |
| |
| if ( m_pCharLangAttrList.is() ) |
| { |
| m_pSerializer->singleElementNS( XML_w, XML_lang, detachFrom( m_pCharLangAttrList ) ); |
| } |
| |
| if (m_nCharTransparence != 0 && m_pColorAttrList && m_aTextEffectsGrabBag.empty()) |
| { |
| std::string_view pVal; |
| m_pColorAttrList->getAsView(FSNS(XML_w, XML_val), pVal); |
| if (!pVal.empty() && pVal != "auto") |
| { |
| m_pSerializer->startElementNS(XML_w14, XML_textFill); |
| m_pSerializer->startElementNS(XML_w14, XML_solidFill); |
| m_pSerializer->startElementNS(XML_w14, XML_srgbClr, FSNS(XML_w14, XML_val), pVal.data()); |
| sal_Int32 nTransparence = m_nCharTransparence * oox::drawingml::MAX_PERCENT / 255.0; |
| m_pSerializer->singleElementNS(XML_w14, XML_alpha, FSNS(XML_w14, XML_val), OString::number(nTransparence)); |
| m_pSerializer->endElementNS(XML_w14, XML_srgbClr); |
| m_pSerializer->endElementNS(XML_w14, XML_solidFill); |
| m_pSerializer->endElementNS(XML_w14, XML_textFill); |
| m_nCharTransparence = 0; |
| } |
| } |
| m_pColorAttrList.clear(); |
| for (const beans::PropertyValue & i : m_aTextEffectsGrabBag) |
| { |
| std::optional<sal_Int32> aElementId = lclGetElementIdForName(i.Name); |
| if(aElementId) |
| { |
| uno::Sequence<beans::PropertyValue> aGrabBagSeq; |
| i.Value >>= aGrabBagSeq; |
| lclProcessRecursiveGrabBag(*aElementId, aGrabBagSeq, m_pSerializer); |
| } |
| } |
| m_aTextEffectsGrabBag.clear(); |
| } |
| |
| void DocxAttributeOutput::EndRunProperties( const SwRedlineData* pRedlineData ) |
| { |
| // Call the 'Redline' function. This will add redline (change-tracking) information that regards to run properties. |
| // This includes changes like 'Bold', 'Underline', 'Strikethrough' etc. |
| |
| // If there is RedlineData present, call WriteCollectedRunProperties() for writing rPr before calling Redline(). |
| // As there will be another rPr for redline and LO might mix both. |
| if(pRedlineData) |
| WriteCollectedRunProperties(); |
| Redline( pRedlineData ); |
| |
| WriteCollectedRunProperties(); |
| |
| // Merge the marks for the ordered elements |
| m_pSerializer->mergeTopMarks(Tag_InitCollectedRunProperties); |
| |
| m_pSerializer->endElementNS( XML_w, XML_rPr ); |
| |
| // write footnotes/endnotes if we have any |
| FootnoteEndnoteReference(); |
| |
| WriteLineBreak(); |
| |
| // merge the properties _before_ the run text (strictly speaking, just |
| // after the start of the run) |
| m_pSerializer->mergeTopMarks(Tag_StartRunProperties, sax_fastparser::MergeMarks::PREPEND); |
| |
| WritePostponedGraphic(); |
| |
| WritePostponedDiagram(); |
| //We need to write w:drawing tag after the w:rPr. |
| WritePostponedChart(); |
| |
| //We need to write w:pict tag after the w:rPr. |
| WritePostponedDMLDrawing(); |
| |
| WritePostponedOLE(); |
| |
| WritePostponedActiveXControl(true); |
| } |
| |
| void DocxAttributeOutput::GetSdtEndBefore(const SdrObject* pSdrObj) |
| { |
| if (!pSdrObj) |
| return; |
| |
| uno::Reference<drawing::XShape> xShape(const_cast<SdrObject*>(pSdrObj)->getUnoShape()); |
| uno::Reference< beans::XPropertySet > xPropSet( xShape, uno::UNO_QUERY ); |
| if( !xPropSet.is() ) |
| return; |
| |
| uno::Reference< beans::XPropertySetInfo > xPropSetInfo = xPropSet->getPropertySetInfo(); |
| uno::Sequence< beans::PropertyValue > aGrabBag; |
| if (xPropSetInfo.is() && xPropSetInfo->hasPropertyByName(u"FrameInteropGrabBag"_ustr)) |
| { |
| xPropSet->getPropertyValue(u"FrameInteropGrabBag"_ustr) >>= aGrabBag; |
| } |
| else if(xPropSetInfo.is() && xPropSetInfo->hasPropertyByName(u"InteropGrabBag"_ustr)) |
| { |
| xPropSet->getPropertyValue(u"InteropGrabBag"_ustr) >>= aGrabBag; |
| } |
| |
| auto pProp = std::find_if(std::cbegin(aGrabBag), std::cend(aGrabBag), |
| [this](const beans::PropertyValue& rProp) { |
| return "SdtEndBefore" == rProp.Name && m_aRunSdt.m_bStartedSdt && !m_bEndCharSdt; }); |
| if (pProp != std::cend(aGrabBag)) |
| pProp->Value >>= m_bEndCharSdt; |
| } |
| |
| void DocxAttributeOutput::WritePostponedGraphic() |
| { |
| for (const auto & rPostponedDiagram : *m_oPostponedGraphic) |
| FlyFrameGraphic(rPostponedDiagram.grfNode, rPostponedDiagram.size, |
| nullptr, nullptr, |
| rPostponedDiagram.pSdrObj); |
| m_oPostponedGraphic.reset(); |
| } |
| |
| void DocxAttributeOutput::WritePostponedDiagram() |
| { |
| for( const auto & rPostponedDiagram : *m_oPostponedDiagrams ) |
| m_rExport.SdrExporter().writeDiagram(rPostponedDiagram.object, |
| *rPostponedDiagram.frame, m_anchorId++); |
| m_oPostponedDiagrams.reset(); |
| } |
| |
| bool DocxAttributeOutput::FootnoteEndnoteRefTag() |
| { |
| if( m_footnoteEndnoteRefTag == 0 ) |
| return false; |
| |
| // output the character style for MS Word's benefit |
| const SwEndNoteInfo& rInfo = m_footnoteEndnoteRefTag == XML_footnoteRef ? |
| m_rExport.m_rDoc.GetFootnoteInfo() : m_rExport.m_rDoc.GetEndNoteInfo(); |
| const SwCharFormat* pCharFormat = rInfo.GetCharFormat( m_rExport.m_rDoc ); |
| if ( pCharFormat ) |
| { |
| const OString aStyleId(m_rExport.m_pStyles->GetStyleId(m_rExport.GetId(pCharFormat))); |
| m_pSerializer->startElementNS(XML_w, XML_rPr); |
| m_pSerializer->singleElementNS(XML_w, XML_rStyle, FSNS(XML_w, XML_val), aStyleId); |
| m_pSerializer->endElementNS( XML_w, XML_rPr ); |
| } |
| |
| if (m_footnoteCustomLabel.isEmpty()) |
| m_pSerializer->singleElementNS(XML_w, m_footnoteEndnoteRefTag); |
| else |
| RunText(m_footnoteCustomLabel); |
| m_footnoteEndnoteRefTag = 0; |
| return true; |
| } |
| |
| /** Output sal_Unicode* as a run text (<t>the text</t>). |
| |
| When bMove is true, update rBegin to point _after_ the end of the text + |
| 1, meaning that it skips one character after the text. This is to make |
| the switch in DocxAttributeOutput::RunText() nicer ;-) |
| */ |
| static bool impl_WriteRunText( FSHelperPtr const & pSerializer, sal_Int32 nTextToken, |
| const sal_Unicode* &rBegin, const sal_Unicode* pEnd, bool bMove = true, |
| const OUString& rSymbolFont = OUString() ) |
| { |
| const sal_Unicode *pBegin = rBegin; |
| |
| // skip one character after the end |
| if ( bMove ) |
| rBegin = pEnd + 1; |
| |
| if ( pBegin >= pEnd ) |
| return false; // we want to write at least one character |
| |
| bool bIsSymbol = !rSymbolFont.isEmpty(); |
| |
| std::u16string_view aView( pBegin, pEnd - pBegin ); |
| if (bIsSymbol) |
| { |
| for (char16_t aChar : aView) |
| { |
| pSerializer->singleElementNS(XML_w, XML_sym, |
| FSNS(XML_w, XML_font), rSymbolFont, |
| FSNS(XML_w, XML_char), OString::number(aChar, 16)); |
| } |
| } |
| else |
| { |
| // we have to add 'preserve' when starting/ending with space |
| if ( *pBegin == ' ' || *( pEnd - 1 ) == ' ' ) |
| pSerializer->startElementNS(XML_w, nTextToken, FSNS(XML_xml, XML_space), "preserve"); |
| else |
| pSerializer->startElementNS(XML_w, nTextToken); |
| |
| pSerializer->writeEscaped( aView ); |
| pSerializer->endElementNS( XML_w, nTextToken ); |
| } |
| |
| return true; |
| } |
| |
| void DocxAttributeOutput::RunText( const OUString& rText, rtl_TextEncoding /*eCharSet*/, const OUString& rSymbolFont ) |
| { |
| if( m_closeHyperlinkInThisRun ) |
| { |
| m_closeHyperlinkInPreviousRun = true; |
| } |
| m_bRunTextIsOn = true; |
| // one text can be split into more <w:t>blah</w:t>'s by line breaks etc. |
| const sal_Unicode *pBegin = rText.getStr(); |
| const sal_Unicode *pEnd = pBegin + rText.getLength(); |
| |
| // the text run is usually XML_t, with the exception of the deleted (and not moved) text |
| sal_Int32 nTextToken = XML_t; |
| |
| bool bMoved = m_pRedlineData && m_pRedlineData->IsMoved() && |
| // tdf#150166 save tracked moving around TOC as w:ins, w:del |
| SwDoc::GetCurTOX(*m_rExport.m_pCurPam->GetPoint()) == nullptr; |
| |
| if ( m_pRedlineData && m_pRedlineData->GetType() == RedlineType::Delete && !bMoved ) |
| { |
| nTextToken = XML_delText; |
| } |
| |
| sal_Unicode prevUnicode = *pBegin; |
| |
| for ( const sal_Unicode *pIt = pBegin; pIt < pEnd; ++pIt ) |
| { |
| switch ( *pIt ) |
| { |
| case 0x09: // tab |
| impl_WriteRunText( m_pSerializer, nTextToken, pBegin, pIt ); |
| m_pSerializer->singleElementNS(XML_w, XML_tab); |
| prevUnicode = *pIt; |
| break; |
| case 0x0b: // line break |
| case static_cast<sal_Unicode>(text::ControlCharacter::LINE_BREAK): |
| { |
| if (impl_WriteRunText( m_pSerializer, nTextToken, pBegin, pIt ) || prevUnicode < 0x0020) |
| { |
| m_pSerializer->singleElementNS(XML_w, XML_br); |
| prevUnicode = *pIt; |
| } |
| } |
| break; |
| case 0x1E: //non-breaking hyphen |
| impl_WriteRunText( m_pSerializer, nTextToken, pBegin, pIt ); |
| m_pSerializer->singleElementNS(XML_w, XML_noBreakHyphen); |
| prevUnicode = *pIt; |
| break; |
| case 0x1F: //soft (on demand) hyphen |
| impl_WriteRunText( m_pSerializer, nTextToken, pBegin, pIt ); |
| m_pSerializer->singleElementNS(XML_w, XML_softHyphen); |
| prevUnicode = *pIt; |
| break; |
| default: |
| if ( *pIt < 0x0020 ) // filter out the control codes |
| { |
| impl_WriteRunText( m_pSerializer, nTextToken, pBegin, pIt ); |
| SAL_INFO("sw.ww8", "Ignored control code in a text run: " << unsigned(*pIt) ); |
| } |
| prevUnicode = *pIt; |
| break; |
| } |
| } |
| |
| impl_WriteRunText( m_pSerializer, nTextToken, pBegin, pEnd, false, rSymbolFont ); |
| } |
| |
| void DocxAttributeOutput::RawText(const OUString& rText, rtl_TextEncoding /*eCharSet*/) |
| { |
| m_sRawText = rText; |
| } |
| |
| void DocxAttributeOutput::StartRuby( const SwTextNode& rNode, sal_Int32 nPos, const SwFormatRuby& rRuby ) |
| { |
| WW8Ruby aWW8Ruby( rNode, rRuby, GetExport() ); |
| SAL_INFO("sw.ww8", "TODO DocxAttributeOutput::StartRuby( const SwTextNode& rNode, const SwFormatRuby& rRuby )" ); |
| EndRun( &rNode, nPos, -1 ); // end run before starting ruby to avoid nested runs, and overlap |
| assert(!m_closeHyperlinkInThisRun); // check that no hyperlink overlaps ruby |
| assert(!m_closeHyperlinkInPreviousRun); |
| m_pSerializer->startElementNS(XML_w, XML_r); |
| m_pSerializer->startElementNS(XML_w, XML_ruby); |
| m_pSerializer->startElementNS(XML_w, XML_rubyPr); |
| |
| m_pSerializer->singleElementNS( XML_w, XML_rubyAlign, |
| FSNS( XML_w, XML_val ), lclConvertWW8JCToOOXMLRubyAlign(aWW8Ruby.GetJC()) ); |
| sal_uInt32 nHps = (aWW8Ruby.GetRubyHeight() + 5) / 10; |
| sal_uInt32 nHpsBaseText = (aWW8Ruby.GetBaseHeight() + 5) / 10; |
| m_pSerializer->singleElementNS(XML_w, XML_hps, FSNS(XML_w, XML_val), OString::number(nHps)); |
| |
| m_pSerializer->singleElementNS( XML_w, XML_hpsRaise, |
| FSNS( XML_w, XML_val ), OString::number(nHpsBaseText) ); |
| |
| m_pSerializer->singleElementNS( XML_w, XML_hpsBaseText, |
| FSNS( XML_w, XML_val ), OString::number(nHpsBaseText) ); |
| |
| lang::Locale aLocale( SwBreakIt::Get()->GetLocale( |
| rNode.GetLang( nPos ) ) ); |
| OUString sLang( LanguageTag::convertToBcp47( aLocale) ); |
| m_pSerializer->singleElementNS(XML_w, XML_lid, FSNS(XML_w, XML_val), sLang); |
| |
| m_pSerializer->endElementNS( XML_w, XML_rubyPr ); |
| |
| m_pSerializer->startElementNS(XML_w, XML_rt); |
| StartRun( nullptr, nPos ); |
| StartRunProperties( ); |
| |
| if (rRuby.GetTextRuby() && rRuby.GetTextRuby()->GetCharFormat()) |
| { |
| const SwCharFormat* pFormat = rRuby.GetTextRuby()->GetCharFormat(); |
| sal_uInt16 nScript = g_pBreakIt->GetBreakIter()->getScriptType(rRuby.GetText(), 0); |
| TypedWhichId<SvxFontItem> nWhichFont = (nScript == i18n::ScriptType::LATIN) ? RES_CHRATR_FONT : RES_CHRATR_CJK_FONT; |
| TypedWhichId<SvxFontHeightItem> nWhichFontSize = (nScript == i18n::ScriptType::LATIN) ? RES_CHRATR_FONTSIZE : RES_CHRATR_CJK_FONTSIZE; |
| |
| CharFont(pFormat->GetFormatAttr(nWhichFont)); |
| CharFontSize(pFormat->GetFormatAttr(nWhichFontSize)); |
| CharFontSize(pFormat->GetFormatAttr(RES_CHRATR_CTL_FONTSIZE)); |
| } |
| |
| EndRunProperties( nullptr ); |
| RunText( rRuby.GetText( ) ); |
| EndRun( &rNode, nPos, -1 ); |
| m_pSerializer->endElementNS( XML_w, XML_rt ); |
| |
| m_pSerializer->startElementNS(XML_w, XML_rubyBase); |
| StartRun( nullptr, nPos ); |
| } |
| |
| void DocxAttributeOutput::EndRuby(const SwTextNode& rNode, sal_Int32 nPos) |
| { |
| SAL_INFO("sw.ww8", "TODO DocxAttributeOutput::EndRuby()" ); |
| EndRun( &rNode, nPos, -1 ); |
| m_pSerializer->endElementNS( XML_w, XML_rubyBase ); |
| m_pSerializer->endElementNS( XML_w, XML_ruby ); |
| m_pSerializer->endElementNS( XML_w, XML_r ); |
| StartRun(nullptr, nPos); // open Run again so OutputTextNode loop can close it |
| } |
| |
| bool DocxAttributeOutput::AnalyzeURL( const OUString& rUrl, const OUString& rTarget, OUString* pLinkURL, OUString* pMark ) |
| { |
| bool bBookMarkOnly = AttributeOutputBase::AnalyzeURL( rUrl, rTarget, pLinkURL, pMark ); |
| if (bBookMarkOnly) |
| *pMark = GetExport().BookmarkToWord(*pMark); |
| |
| if (!pMark->isEmpty() && (bBookMarkOnly || rTarget.isEmpty())) |
| { |
| OUString sURL = *pLinkURL; |
| |
| if ( bBookMarkOnly ) |
| sURL = FieldString( ww::eHYPERLINK ); |
| else |
| sURL = FieldString( ww::eHYPERLINK ) + "\"" + sURL + "\""; |
| |
| sURL += " \\l \"" + *pMark + "\""; |
| |
| if ( !rTarget.isEmpty() ) |
| sURL += " \\n " + rTarget; |
| |
| *pLinkURL = sURL; |
| } |
| |
| return bBookMarkOnly; |
| } |
| |
| void DocxAttributeOutput::WriteBookmarkInActParagraph( const OUString& rName, sal_Int32 nFirstRunPos, sal_Int32 nLastRunPos ) |
| { |
| m_aBookmarksOfParagraphStart.insert(std::pair<sal_Int32, OUString>(nFirstRunPos, rName)); |
| m_aBookmarksOfParagraphEnd.insert(std::pair<sal_Int32, OUString>(nLastRunPos, rName)); |
| } |
| |
| bool DocxAttributeOutput::StartURL(const OUString& rUrl, const OUString& rTarget, const OUString& rName) |
| { |
| OUString sMark; |
| OUString sUrl; |
| |
| bool bBookmarkOnly = AnalyzeURL( rUrl, rTarget, &sUrl, &sMark ); |
| |
| m_hyperLinkAnchor = sMark; |
| |
| if (!sMark.isEmpty() && !bBookmarkOnly && rTarget.isEmpty()) |
| { |
| m_rExport.OutputField( nullptr, ww::eHYPERLINK, sUrl ); |
| } |
| else |
| { |
| // Output a hyperlink XML element |
| m_pHyperlinkAttrList = FastSerializerHelper::createAttrList(); |
| |
| if ( !bBookmarkOnly ) |
| { |
| OUString sId = GetExport().GetFilter().addRelation( m_pSerializer->getOutputStream(), |
| oox::getRelationship(Relationship::HYPERLINK), |
| sUrl, true ); |
| |
| m_pHyperlinkAttrList->add(FSNS(XML_r, XML_id), sId); |
| if (!sMark.isEmpty()) |
| { |
| sMark = sMark.replace(' ', '_'); |
| m_pHyperlinkAttrList->add(FSNS(XML_w, XML_anchor), sMark); |
| } |
| } |
| else |
| { |
| // Is this a link to a sequence? Then try to replace that with a |
| // normal bookmark, as Word won't understand our special |
| // <seqname>!<index>|sequence syntax. |
| if (sMark.endsWith("|sequence")) |
| { |
| sal_Int32 nPos = sMark.indexOf('!'); |
| if (nPos != -1) |
| { |
| // Extract <seqname>, the field instruction text has the name quoted. |
| OUString aSequenceName = sMark.copy(0, nPos); |
| // Extract <index>. |
| sal_uInt32 nIndex = o3tl::toUInt32(sMark.subView(nPos + 1, sMark.getLength() - nPos - sizeof("|sequence"))); |
| auto it = m_aSeqBookmarksNames.find(aSequenceName); |
| if (it != m_aSeqBookmarksNames.end()) |
| { |
| std::vector<OUString>& rNames = it->second; |
| if (rNames.size() > nIndex) |
| // We know the bookmark name for this sequence and this index, do the replacement. |
| sMark = rNames[nIndex]; |
| } |
| } |
| } |
| else if (sMark.endsWith("|toxmark")) |
| { |
| if (auto const it = GetExport().m_TOXMarkBookmarksByURL.find(sMark); |
| it != GetExport().m_TOXMarkBookmarksByURL.end()) |
| { |
| sMark = it->second; |
| } |
| } |
| // Spaces are prohibited in bookmark name. |
| sMark = sMark.replace(' ', '_'); |
| m_pHyperlinkAttrList->add( FSNS( XML_w, XML_anchor ), sMark ); |
| } |
| |
| if ( !rTarget.isEmpty() ) |
| { |
| m_pHyperlinkAttrList->add(FSNS(XML_w, XML_tgtFrame), rTarget); |
| } |
| else if (!rName.isEmpty()) |
| { |
| m_pHyperlinkAttrList->add(FSNS(XML_w, XML_tooltip), rName); |
| } |
| } |
| |
| return true; |
| } |
| |
| bool DocxAttributeOutput::EndURL(bool const) |
| { |
| m_closeHyperlinkInThisRun = true; |
| if (m_nHyperLinkCount.back() > 0 && !m_hyperLinkAnchor.isEmpty() |
| && m_hyperLinkAnchor.startsWith("_Toc")) |
| { |
| m_endPageRef = true; |
| } |
| return true; |
| } |
| |
| void DocxAttributeOutput::FieldVanish(const OUString& rText, |
| ww::eField const eType, OUString const*const pBookmarkName) |
| { |
| WriteField_Impl(nullptr, eType, rText, FieldFlags::All, pBookmarkName); |
| } |
| |
| // The difference between 'Redline' and 'StartRedline'+'EndRedline' is that: |
| // 'Redline' is used for tracked changes of formatting information of a run like Bold, Underline. (the '<w:rPrChange>' is inside the 'run' node) |
| // 'StartRedline' is used to output tracked changes of run insertion and deletion (the run is inside the '<w:ins>' node) |
| void DocxAttributeOutput::Redline( const SwRedlineData* pRedlineData) |
| { |
| if ( !pRedlineData ) |
| return; |
| |
| bool bRemovePersonalInfo = SvtSecurityOptions::IsOptionSet( |
| SvtSecurityOptions::EOption::DocWarnRemovePersonalInfo ) && !SvtSecurityOptions::IsOptionSet( |
| SvtSecurityOptions::EOption::DocWarnKeepRedlineInfo); |
| |
| OString aId( OString::number( pRedlineData->GetSeqNo() ) ); |
| const OUString &rAuthor( SW_MOD()->GetRedlineAuthor( pRedlineData->GetAuthor() ) ); |
| const DateTime aDateTime = pRedlineData->GetTimeStamp(); |
| bool bNoDate = bRemovePersonalInfo || |
| ( aDateTime.GetYear() == 1970 && aDateTime.GetMonth() == 1 && aDateTime.GetDay() == 1 ); |
| |
| switch( pRedlineData->GetType() ) |
| { |
| case RedlineType::Insert: |
| break; |
| |
| case RedlineType::Delete: |
| break; |
| |
| case RedlineType::Format: |
| { |
| rtl::Reference<sax_fastparser::FastAttributeList> pAttributeList |
| = sax_fastparser::FastSerializerHelper::createAttrList(); |
| |
| pAttributeList->add(FSNS( XML_w, XML_id ), aId); |
| pAttributeList->add(FSNS( XML_w, XML_author ), bRemovePersonalInfo |
| ? "Author" + OString::number( GetExport().GetInfoID(rAuthor) ) |
| : rAuthor.toUtf8()); |
| if (!bNoDate) |
| pAttributeList->add(FSNS( XML_w, XML_date ), DateTimeToOString( aDateTime )); |
| m_pSerializer->startElementNS( XML_w, XML_rPrChange, pAttributeList ); |
| |
| // Check if there is any extra data stored in the redline object |
| if (pRedlineData->GetExtraData()) |
| { |
| const SwRedlineExtraData* pExtraData = pRedlineData->GetExtraData(); |
| const SwRedlineExtraData_FormatColl* pFormattingChanges = dynamic_cast<const SwRedlineExtraData_FormatColl*>(pExtraData); |
| |
| // Check if the extra data is of type 'formatting changes' |
| if (pFormattingChanges) |
| { |
| // Get the item set that holds all the changes properties |
| const SfxItemSet *pChangesSet = pFormattingChanges->GetItemSet(); |
| if (pChangesSet) |
| { |
| m_pSerializer->mark(Tag_Redline_1); |
| |
| m_pSerializer->startElementNS(XML_w, XML_rPr); |
| |
| // Output the redline item set |
| if (pChangesSet) |
| m_rExport.OutputItemSet( *pChangesSet, false, true, i18n::ScriptType::LATIN, m_rExport.m_bExportModeRTF ); |
| |
| m_pSerializer->endElementNS( XML_w, XML_rPr ); |
| |
| m_pSerializer->mergeTopMarks(Tag_Redline_1, sax_fastparser::MergeMarks::PREPEND); |
| } |
| } |
| } |
| |
| m_pSerializer->endElementNS( XML_w, XML_rPrChange ); |
| break; |
| } |
| case RedlineType::ParagraphFormat: |
| { |
| rtl::Reference<sax_fastparser::FastAttributeList> pAttributeList |
| = sax_fastparser::FastSerializerHelper::createAttrList(); |
| |
| pAttributeList->add(FSNS( XML_w, XML_id ), aId); |
| pAttributeList->add(FSNS( XML_w, XML_author ), bRemovePersonalInfo |
| ? "Author" + OString::number( GetExport().GetInfoID(rAuthor) ) |
| : rAuthor.toUtf8()); |
| if (!bNoDate) |
| pAttributeList->add(FSNS( XML_w, XML_date ), DateTimeToOString( aDateTime )); |
| m_pSerializer->startElementNS( XML_w, XML_pPrChange, pAttributeList ); |
| |
| // Check if there is any extra data stored in the redline object |
| if (pRedlineData->GetExtraData()) |
| { |
| const SwRedlineExtraData* pExtraData = pRedlineData->GetExtraData(); |
| const SwRedlineExtraData_FormatColl* pFormattingChanges = dynamic_cast<const SwRedlineExtraData_FormatColl*>(pExtraData); |
| |
| // Check if the extra data is of type 'formatting changes' |
| if (pFormattingChanges) |
| { |
| // Get the item set that holds all the changes properties |
| const SfxItemSet *pChangesSet = pFormattingChanges->GetItemSet(); |
| const OUString & sParaStyleName = pFormattingChanges->GetFormatName(); |
| if (pChangesSet || !sParaStyleName.isEmpty()) |
| { |
| m_pSerializer->mark(Tag_Redline_2); |
| |
| m_pSerializer->startElementNS(XML_w, XML_pPr); |
| |
| if (!sParaStyleName.isEmpty()) |
| { |
| OString sStyleName; |
| if (auto format = m_rExport.m_rDoc.FindTextFormatCollByName(sParaStyleName)) |
| if (auto slot = m_rExport.m_pStyles->GetSlot(format); slot != 0xfff) |
| sStyleName = m_rExport.m_pStyles->GetStyleId(slot); |
| // The resolved style name can be empty at this point, sParaStyleName can be |
| // an arbitrary string from the original document. |
| // Note that Word does *not* roundtrip unknown style names in redlines! |
| if (sStyleName.isEmpty()) |
| sStyleName = MSWordStyles::CreateStyleId(sParaStyleName); |
| if (!sStyleName.isEmpty()) |
| m_pSerializer->singleElementNS(XML_w, XML_pStyle, FSNS(XML_w, XML_val), sStyleName); |
| } |
| |
| // The 'm_rExport.SdrExporter().getFlyAttrList()', 'm_pParagraphSpacingAttrList' are used to hold information |
| // that should be collected by different properties in the core, and are all flushed together |
| // to the DOCX when the function 'WriteCollectedParagraphProperties' gets called. |
| // So we need to store the current status of these lists, so that we can revert back to them when |
| // we are done exporting the redline attributes. |
| auto pFlyAttrList_Original(detachFrom(m_rExport.SdrExporter().getFlyAttrList())); |
| auto pLRSpaceAttrList_Original(detachFrom(m_pLRSpaceAttrList)); |
| auto pParagraphSpacingAttrList_Original(detachFrom(m_pParagraphSpacingAttrList)); |
| |
| // Output the redline item set |
| if (pChangesSet) |
| m_rExport.OutputItemSet( *pChangesSet, true, false, i18n::ScriptType::LATIN, m_rExport.m_bExportModeRTF ); |
| |
| // Write the collected paragraph properties that are stored in 'm_rExport.SdrExporter().getFlyAttrList()', 'm_pParagraphSpacingAttrList' |
| WriteCollectedParagraphProperties(); |
| |
| // Revert back the original values that were stored in 'm_rExport.SdrExporter().getFlyAttrList()', 'm_pParagraphSpacingAttrList' |
| m_rExport.SdrExporter().getFlyAttrList() = std::move(pFlyAttrList_Original); |
| m_pLRSpaceAttrList = std::move(pLRSpaceAttrList_Original); |
| m_pParagraphSpacingAttrList = std::move(pParagraphSpacingAttrList_Original); |
| |
| m_pSerializer->endElementNS( XML_w, XML_pPr ); |
| |
| m_pSerializer->mergeTopMarks(Tag_Redline_2, sax_fastparser::MergeMarks::PREPEND); |
| } |
| } |
| } |
| m_pSerializer->endElementNS( XML_w, XML_pPrChange ); |
| break; |
| } |
| default: |
| SAL_WARN("sw.ww8", "Unhandled redline type for export " << SwRedlineTypeToOUString(pRedlineData->GetType())); |
| break; |
| } |
| } |
| |
| // The difference between 'Redline' and 'StartRedline'+'EndRedline' is that: |
| // 'Redline' is used for tracked changes of formatting information of a run like Bold, Underline. (the '<w:rPrChange>' is inside the 'run' node) |
| // 'StartRedline' is used to output tracked changes of run insertion and deletion (the run is inside the '<w:ins>' node) |
| void DocxAttributeOutput::StartRedline( const SwRedlineData * pRedlineData, bool bLastRun ) |
| { |
| if ( !pRedlineData ) |
| return; |
| |
| // write out stack of this redline recursively (first the oldest) |
| if ( !bLastRun ) |
| StartRedline( pRedlineData->Next(), false ); |
| |
| OString aId( OString::number( m_nRedlineId++ ) ); |
| |
| bool bRemovePersonalInfo = SvtSecurityOptions::IsOptionSet( |
| SvtSecurityOptions::EOption::DocWarnRemovePersonalInfo ) && !SvtSecurityOptions::IsOptionSet( |
| SvtSecurityOptions::EOption::DocWarnKeepRedlineInfo); |
| |
| const OUString &rAuthor( SW_MOD()->GetRedlineAuthor( pRedlineData->GetAuthor() ) ); |
| OString aAuthor( OUStringToOString( bRemovePersonalInfo |
| ? "Author" + OUString::number( GetExport().GetInfoID(rAuthor) ) |
| : rAuthor, RTL_TEXTENCODING_UTF8 ) ); |
| |
| const DateTime aDateTime = pRedlineData->GetTimeStamp(); |
| bool bNoDate = bRemovePersonalInfo || |
| ( aDateTime.GetYear() == 1970 && aDateTime.GetMonth() == 1 && aDateTime.GetDay() == 1 ); |
| bool bMoved = pRedlineData->IsMoved() && |
| // tdf#150166 save tracked moving around TOC as w:ins, w:del |
| SwDoc::GetCurTOX(*m_rExport.m_pCurPam->GetPoint()) == nullptr; |
| switch ( pRedlineData->GetType() ) |
| { |
| case RedlineType::Insert: |
| case RedlineType::Delete: |
| { |
| sal_Int32 eElement = RedlineType::Insert == pRedlineData->GetType() |
| ? ( bMoved ? XML_moveTo : XML_ins ) |
| : ( bMoved ? XML_moveFrom : XML_del ); |
| if ( bNoDate ) |
| m_pSerializer->startElementNS( XML_w, eElement, |
| FSNS( XML_w, XML_id ), aId, |
| FSNS( XML_w, XML_author ), aAuthor ); |
| else |
| m_pSerializer->startElementNS( XML_w, eElement, |
| FSNS( XML_w, XML_id ), aId, |
| FSNS( XML_w, XML_author ), aAuthor, |
| FSNS( XML_w, XML_date ), DateTimeToOString( aDateTime ) ); |
| break; |
| } |
| case RedlineType::Format: |
| SAL_INFO("sw.ww8", "TODO DocxAttributeOutput::StartRedline()" ); |
| break; |
| default: |
| break; |
| } |
| } |
| |
| void DocxAttributeOutput::EndRedline( const SwRedlineData * pRedlineData, bool bLastRun ) |
| { |
| if ( !pRedlineData || m_bWritingField ) |
| return; |
| |
| bool bMoved = pRedlineData->IsMoved() && |
| // tdf#150166 save tracked moving around TOC as w:ins, w:del |
| SwDoc::GetCurTOX(*m_rExport.m_pCurPam->GetPoint()) == nullptr; |
| switch ( pRedlineData->GetType() ) |
| { |
| case RedlineType::Insert: |
| m_pSerializer->endElementNS( XML_w, bMoved ? XML_moveTo : XML_ins ); |
| break; |
| |
| case RedlineType::Delete: |
| m_pSerializer->endElementNS( XML_w, bMoved ? XML_moveFrom : XML_del ); |
| break; |
| |
| case RedlineType::Format: |
| SAL_INFO("sw.ww8", "TODO DocxAttributeOutput::EndRedline()" ); |
| break; |
| default: |
| break; |
| } |
| |
| // write out stack of this redline recursively (first the newest) |
| if ( !bLastRun ) |
| EndRedline( pRedlineData->Next(), false ); |
| } |
| |
| void DocxAttributeOutput::FormatDrop( const SwTextNode& /*rNode*/, const SwFormatDrop& /*rSwFormatDrop*/, sal_uInt16 /*nStyle*/, ww8::WW8TableNodeInfo::Pointer_t /*pTextNodeInfo*/, ww8::WW8TableNodeInfoInner::Pointer_t ) |
| { |
| SAL_INFO("sw.ww8", "TODO DocxAttributeOutput::FormatDrop( const SwTextNode& rNode, const SwFormatDrop& rSwFormatDrop, sal_uInt16 nStyle )" ); |
| } |
| |
| void DocxAttributeOutput::ParagraphStyle( sal_uInt16 nStyle ) |
| { |
| OString aStyleId(m_rExport.m_pStyles->GetStyleId(nStyle)); |
| |
| m_pSerializer->singleElementNS(XML_w, XML_pStyle, FSNS(XML_w, XML_val), aStyleId); |
| } |
| |
| static void impl_borderLine( FSHelperPtr const & pSerializer, sal_Int32 elementToken, const SvxBorderLine* pBorderLine, sal_uInt16 nDist, |
| bool bWriteShadow, const table::BorderLine2* pStyleProps = nullptr) |
| { |
| // Compute val attribute value |
| // Can be one of: |
| // single, double, |
| // basicWideOutline, basicWideInline |
| // OOXml also supports those types of borders, but we'll try to play with the first ones. |
| // thickThinMediumGap, thickThinLargeGap, thickThinSmallGap |
| // thinThickLargeGap, thinThickMediumGap, thinThickSmallGap |
| const char* pVal = "nil"; |
| if ( pBorderLine && !pBorderLine->isEmpty( ) ) |
| { |
| switch (pBorderLine->GetBorderLineStyle()) |
| { |
| case SvxBorderLineStyle::SOLID: |
| pVal = "single"; |
| break; |
| case SvxBorderLineStyle::DOTTED: |
| pVal = "dotted"; |
| break; |
| case SvxBorderLineStyle::DASHED: |
| pVal = "dashed"; |
| break; |
| case SvxBorderLineStyle::DOUBLE: |
| case SvxBorderLineStyle::DOUBLE_THIN: |
| pVal = "double"; |
| break; |
| case SvxBorderLineStyle::THINTHICK_SMALLGAP: |
| pVal = "thinThickSmallGap"; |
| break; |
| case SvxBorderLineStyle::THINTHICK_MEDIUMGAP: |
| pVal = "thinThickMediumGap"; |
| break; |
| case SvxBorderLineStyle::THINTHICK_LARGEGAP: |
| pVal = "thinThickLargeGap"; |
| break; |
| case SvxBorderLineStyle::THICKTHIN_SMALLGAP: |
| pVal = "thickThinSmallGap"; |
| break; |
| case SvxBorderLineStyle::THICKTHIN_MEDIUMGAP: |
| pVal = "thickThinMediumGap"; |
| break; |
| case SvxBorderLineStyle::THICKTHIN_LARGEGAP: |
| pVal = "thickThinLargeGap"; |
| break; |
| case SvxBorderLineStyle::EMBOSSED: |
| pVal = "threeDEmboss"; |
| break; |
| case SvxBorderLineStyle::ENGRAVED: |
| pVal = "threeDEngrave"; |
| break; |
| case SvxBorderLineStyle::OUTSET: |
| pVal = "outset"; |
| break; |
| case SvxBorderLineStyle::INSET: |
| pVal = "inset"; |
| break; |
| case SvxBorderLineStyle::FINE_DASHED: |
| pVal = "dashSmallGap"; |
| break; |
| case SvxBorderLineStyle::DASH_DOT: |
| pVal = "dotDash"; |
| break; |
| case SvxBorderLineStyle::DASH_DOT_DOT: |
| pVal = "dotDotDash"; |
| break; |
| case SvxBorderLineStyle::NONE: |
| default: |
| break; |
| } |
| } |
| else if (!pStyleProps || !pStyleProps->LineWidth) |
| // no line, and no line set by the style either: |
| // there is no need to write the property |
| return; |
| |
| // compare the properties with the theme properties before writing them: |
| // if they are equal, it means that they were style-defined and there is |
| // no need to write them. |
| if (pStyleProps && pBorderLine && !pBorderLine->isEmpty() |
| && pBorderLine->GetBorderLineStyle() |
| == static_cast<SvxBorderLineStyle>(pStyleProps->LineStyle) |
| && pBorderLine->GetColor() == Color(ColorTransparency, pStyleProps->Color) |
| && pBorderLine->GetWidth() == o3tl::toTwips(pStyleProps->LineWidth, o3tl::Length::mm100)) |
| { |
| return; |
| } |
| |
| rtl::Reference<FastAttributeList> pAttr = FastSerializerHelper::createAttrList(); |
| pAttr->add( FSNS( XML_w, XML_val ), pVal ); |
| |
| if ( pBorderLine && !pBorderLine->isEmpty() ) |
| { |
| // Compute the sz attribute |
| |
| double const fConverted( ::editeng::ConvertBorderWidthToWord( |
| pBorderLine->GetBorderLineStyle(), pBorderLine->GetWidth())); |
| // The unit is the 8th of point |
| sal_Int32 nWidth = sal_Int32( fConverted / 2.5 ); |
| const sal_Int32 nMinWidth = 2; |
| const sal_Int32 nMaxWidth = 96; |
| |
| if ( nWidth > nMaxWidth ) |
| nWidth = nMaxWidth; |
| else if ( nWidth < nMinWidth ) |
| nWidth = nMinWidth; |
| |
| pAttr->add( FSNS( XML_w, XML_sz ), OString::number( nWidth ) ); |
| |
| // Get the distance (in pt) |
| pAttr->add(FSNS(XML_w, XML_space), OString::number(rtl::math::round(nDist / 20.0))); |
| |
| // Get the color code as an RRGGBB hex value |
| OString sColor( msfilter::util::ConvertColor( pBorderLine->GetColor( ) ) ); |
| pAttr->add( FSNS(XML_w, XML_color), sColor); |
| |
| model::ComplexColor const& rComplexColor = pBorderLine->getComplexColor(); |
| lclAddThemeColorAttributes(pAttr, rComplexColor); |
| } |
| |
| if (bWriteShadow) |
| { |
| // Set the shadow value |
| pAttr->add( FSNS( XML_w, XML_shadow ), "1" ); |
| } |
| |
| pSerializer->singleElementNS( XML_w, elementToken, pAttr ); |
| } |
| |
| static OutputBorderOptions lcl_getTableCellBorderOptions(bool bEcma) |
| { |
| OutputBorderOptions rOptions; |
| |
| rOptions.tag = XML_tcBorders; |
| rOptions.bUseStartEnd = !bEcma; |
| rOptions.bWriteTag = true; |
| rOptions.bWriteDistance = false; |
| |
| return rOptions; |
| } |
| |
| static OutputBorderOptions lcl_getBoxBorderOptions() |
| { |
| OutputBorderOptions rOptions; |
| |
| rOptions.tag = XML_pBdr; |
| rOptions.bUseStartEnd = false; |
| rOptions.bWriteTag = false; |
| rOptions.bWriteDistance = true; |
| |
| return rOptions; |
| } |
| |
| static void impl_borders( FSHelperPtr const & pSerializer, |
| const SvxBoxItem& rBox, |
| const OutputBorderOptions& rOptions, |
| std::map<SvxBoxItemLine, |
| css::table::BorderLine2> &rTableStyleConf, |
| ww8::Frame* pFramePr = nullptr) |
| { |
| static const SvxBoxItemLine aBorders[] = |
| { |
| SvxBoxItemLine::TOP, SvxBoxItemLine::LEFT, SvxBoxItemLine::BOTTOM, SvxBoxItemLine::RIGHT |
| }; |
| |
| const sal_Int32 aXmlElements[] = |
| { |
| XML_top, |
| rOptions.bUseStartEnd ? XML_start : XML_left, |
| XML_bottom, |
| rOptions.bUseStartEnd ? XML_end : XML_right |
| }; |
| bool tagWritten = false; |
| const SvxBoxItemLine* pBrd = aBorders; |
| |
| for( int i = 0; i < 4; ++i, ++pBrd ) |
| { |
| const SvxBorderLine* pLn = rBox.GetLine( *pBrd ); |
| const table::BorderLine2 *aStyleProps = nullptr; |
| auto it = rTableStyleConf.find( *pBrd ); |
| if( it != rTableStyleConf.end() ) |
| aStyleProps = &(it->second); |
| |
| if (!tagWritten && rOptions.bWriteTag) |
| { |
| pSerializer->startElementNS(XML_w, rOptions.tag); |
| tagWritten = true; |
| } |
| |
| bool bWriteShadow = false; |
| if (rOptions.aShadowLocation == SvxShadowLocation::NONE) |
| { |
| // The border has no shadow |
| } |
| else if (rOptions.aShadowLocation == SvxShadowLocation::BottomRight) |
| { |
| // Special case of 'Bottom-Right' shadow: |
| // If the shadow location is 'Bottom-Right' - then turn on the shadow |
| // for ALL the sides. This is because in Word - if you select a shadow |
| // for a border - it turn on the shadow for ALL the sides (but shows only |
| // the bottom-right one). |
| // This is so that no information will be lost if passed through LibreOffice |
| bWriteShadow = true; |
| } |
| else |
| { |
| // If there is a shadow, and it's not the regular 'Bottom-Right', |
| // then write only the 'shadowed' sides of the border |
| if ( |
| ((rOptions.aShadowLocation == SvxShadowLocation::TopLeft || rOptions.aShadowLocation == SvxShadowLocation::TopRight ) && *pBrd == SvxBoxItemLine::TOP ) || |
| ((rOptions.aShadowLocation == SvxShadowLocation::TopLeft || rOptions.aShadowLocation == SvxShadowLocation::BottomLeft) && *pBrd == SvxBoxItemLine::LEFT ) || |
| ((rOptions.aShadowLocation == SvxShadowLocation::BottomLeft ) && *pBrd == SvxBoxItemLine::BOTTOM) || |
| ((rOptions.aShadowLocation == SvxShadowLocation::TopRight ) && *pBrd == SvxBoxItemLine::RIGHT ) |
| ) |
| { |
| bWriteShadow = true; |
| } |
| } |
| |
| sal_uInt16 nDist = 0; |
| if (rOptions.bWriteDistance) |
| { |
| if (rOptions.pDistances) |
| { |
| if ( *pBrd == SvxBoxItemLine::TOP) |
| nDist = rOptions.pDistances->nTop; |
| else if ( *pBrd == SvxBoxItemLine::LEFT) |
| nDist = rOptions.pDistances->nLeft; |
| else if ( *pBrd == SvxBoxItemLine::BOTTOM) |
| nDist = rOptions.pDistances->nBottom; |
| else if ( *pBrd == SvxBoxItemLine::RIGHT) |
| nDist = rOptions.pDistances->nRight; |
| } |
| else |
| { |
| nDist = rBox.GetDistance(*pBrd); |
| } |
| } |
| |
| if (pFramePr) |
| { |
| assert(rOptions.bWriteDistance && !rOptions.pDistances); |
| |
| // In addition to direct properties, and paragraph styles, |
| // for framePr-floated paragraphs the frame borders also affect the exported values. |
| |
| // For border spacing, there is a special situation to consider |
| // because a compat setting ignores left/right paragraph spacing on layout. |
| const SwFrameFormat& rFormat = pFramePr->GetFrameFormat(); |
| const SvxBoxItem& rFramePrBox = rFormat.GetBox(); |
| const IDocumentSettingAccess& rIDSA = rFormat.GetDoc()->getIDocumentSettingAccess(); |
| if (rIDSA.get(DocumentSettingId::INVERT_BORDER_SPACING) |
| && (*pBrd == SvxBoxItemLine::LEFT || *pBrd == SvxBoxItemLine::RIGHT)) |
| { |
| // only the frame's border spacing affects layout - so use that value instead. |
| nDist = rFramePrBox.GetDistance(*pBrd); |
| } |
| else |
| { |
| nDist += rFramePrBox.GetDistance(*pBrd); |
| } |
| |
| // Unless the user added a paragraph border, the border normally comes from the frame. |
| if (!pLn) |
| pLn = rFramePrBox.GetLine(*pBrd); |
| } |
| |
| impl_borderLine( pSerializer, aXmlElements[i], pLn, nDist, bWriteShadow, aStyleProps ); |
| } |
| if (tagWritten && rOptions.bWriteTag) { |
| pSerializer->endElementNS( XML_w, rOptions.tag ); |
| } |
| } |
| |
| void DocxAttributeOutput::ImplCellMargins( FSHelperPtr const & pSerializer, const SvxBoxItem& rBox, sal_Int32 tag, bool bUseStartEnd, const SvxBoxItem* pDefaultMargins) |
| { |
| static const SvxBoxItemLine aBorders[] = |
| { |
| SvxBoxItemLine::TOP, SvxBoxItemLine::LEFT, SvxBoxItemLine::BOTTOM, SvxBoxItemLine::RIGHT |
| }; |
| |
| const sal_Int32 aXmlElements[] = |
| { |
| XML_top, |
| bUseStartEnd ? XML_start : XML_left, |
| XML_bottom, |
| bUseStartEnd ? XML_end : XML_right |
| }; |
| bool tagWritten = false; |
| const SvxBoxItemLine* pBrd = aBorders; |
| for( int i = 0; i < 4; ++i, ++pBrd ) |
| { |
| sal_Int32 nDist = sal_Int32( rBox.GetDistance( *pBrd ) ); |
| |
| if (pDefaultMargins) |
| { |
| // Skip output if cell margin == table default margin |
| if (sal_Int32( pDefaultMargins->GetDistance( *pBrd ) ) == nDist) |
| continue; |
| } |
| |
| if (!tagWritten) { |
| pSerializer->startElementNS(XML_w, tag); |
| tagWritten = true; |
| } |
| pSerializer->singleElementNS( XML_w, aXmlElements[i], |
| FSNS( XML_w, XML_w ), OString::number(nDist), |
| FSNS( XML_w, XML_type ), "dxa" ); |
| } |
| if (tagWritten) { |
| pSerializer->endElementNS( XML_w, tag ); |
| } |
| } |
| |
| void DocxAttributeOutput::TableCellProperties( ww8::WW8TableNodeInfoInner::Pointer_t const & pTableTextNodeInfoInner, sal_uInt32 nCell, sal_uInt32 nRow ) |
| { |
| m_pSerializer->startElementNS(XML_w, XML_tcPr); |
| |
| const SwTableBox *pTableBox = pTableTextNodeInfoInner->getTableBox( ); |
| |
| bool const bEcma = GetExport().GetFilter().getVersion() == oox::core::ECMA_376_1ST_EDITION; |
| |
| // Output any table cell redlines if there are any attached to this specific cell |
| TableCellRedline( pTableTextNodeInfoInner ); |
| |
| // Cell preferred width |
| SwTwips nWidth = GetGridCols( pTableTextNodeInfoInner )->at( nCell ); |
| if ( nCell ) |
| nWidth = nWidth - GetGridCols( pTableTextNodeInfoInner )->at( nCell - 1 ); |
| m_pSerializer->singleElementNS( XML_w, XML_tcW, |
| FSNS( XML_w, XML_w ), OString::number(nWidth), |
| FSNS( XML_w, XML_type ), "dxa" ); |
| |
| // Horizontal spans |
| const SwWriteTableRows& rRows = m_xTableWrt->GetRows( ); |
| if (nRow >= rRows.size()) |
| SAL_WARN("sw.ww8", "DocxAttributeOutput::TableCellProperties: out of range row: " << nRow); |
| else |
| { |
| SwWriteTableRow *pRow = rRows[ nRow ].get(); |
| const SwWriteTableCells& rTableCells = pRow->GetCells(); |
| if (nCell < rTableCells.size() ) |
| { |
| const SwWriteTableCell& rCell = *rTableCells[nCell]; |
| const sal_uInt16 nColSpan = rCell.GetColSpan(); |
| if ( nColSpan > 1 ) |
| m_pSerializer->singleElementNS( XML_w, XML_gridSpan, |
| FSNS( XML_w, XML_val ), OString::number(nColSpan) ); |
| } |
| } |
| |
| // Vertical merges |
| ww8::RowSpansPtr xRowSpans = pTableTextNodeInfoInner->getRowSpansOfRow(); |
| sal_Int32 vSpan = (*xRowSpans)[nCell]; |
| if ( vSpan > 1 ) |
| { |
| m_pSerializer->singleElementNS(XML_w, XML_vMerge, FSNS(XML_w, XML_val), "restart"); |
| } |
| else if ( vSpan < 0 ) |
| { |
| m_pSerializer->singleElementNS(XML_w, XML_vMerge, FSNS(XML_w, XML_val), "continue"); |
| } |
| |
| if (const SfxGrabBagItem* pItem = pTableBox->GetFrameFormat()->GetAttrSet().GetItem<SfxGrabBagItem>(RES_FRMATR_GRABBAG)) |
| { |
| const std::map<OUString, uno::Any>& rGrabBag = pItem->GetGrabBag(); |
| std::map<OUString, uno::Any>::const_iterator it = rGrabBag.find(u"CellCnfStyle"_ustr); |
| if (it != rGrabBag.end()) |
| { |
| uno::Sequence<beans::PropertyValue> aAttributes = it->second.get< uno::Sequence<beans::PropertyValue> >(); |
| m_pTableStyleExport->CnfStyle(aAttributes); |
| } |
| } |
| |
| |
| const SvxBoxItem& rBox = pTableBox->GetFrameFormat( )->GetBox( ); |
| const SvxBoxItem& rDefaultBox = (*m_TableFirstCells.rbegin())->getTableBox( )->GetFrameFormat( )->GetBox( ); |
| { |
| // The cell borders |
| impl_borders(m_pSerializer, rBox, lcl_getTableCellBorderOptions(bEcma), |
| m_aTableStyleConfs.back()); |
| } |
| |
| TableBackgrounds( pTableTextNodeInfoInner ); |
| |
| { |
| // Cell margins |
| DocxAttributeOutput::ImplCellMargins( m_pSerializer, rBox, XML_tcMar, !bEcma, &rDefaultBox ); |
| } |
| |
| TableVerticalCell( pTableTextNodeInfoInner ); |
| |
| m_pSerializer->endElementNS( XML_w, XML_tcPr ); |
| } |
| |
| void DocxAttributeOutput::InitTableHelper( ww8::WW8TableNodeInfoInner::Pointer_t const & pTableTextNodeInfoInner ) |
| { |
| const SwTable* pTable = pTableTextNodeInfoInner->getTable(); |
| if (m_xTableWrt && pTable == m_xTableWrt->GetTable()) |
| return; |
| |
| tools::Long nPageSize = 0; |
| bool bRelBoxSize = false; |
| |
| // Create the SwWriteTable instance to use col spans (and maybe other infos) |
| GetTablePageSize( pTableTextNodeInfoInner.get(), nPageSize, bRelBoxSize ); |
| |
| const SwFrameFormat *pFormat = pTable->GetFrameFormat( ); |
| const sal_uInt32 nTableSz = static_cast<sal_uInt32>(pFormat->GetFrameSize( ).GetWidth( )); |
| |
| const SwHTMLTableLayout *pLayout = pTable->GetHTMLTableLayout(); |
| if( pLayout && pLayout->IsExportable() ) |
| m_xTableWrt.reset(new SwWriteTable(pTable, pLayout)); |
| else |
| m_xTableWrt.reset(new SwWriteTable(pTable, pTable->GetTabLines(), nPageSize, nTableSz, false)); |
| } |
| |
| void DocxAttributeOutput::StartTable( ww8::WW8TableNodeInfoInner::Pointer_t const & pTableTextNodeInfoInner ) |
| { |
| m_aTableStyleConfs.emplace_back(); |
| |
| // In case any paragraph SDT's are open, close them here. |
| EndParaSdtBlock(); |
| |
| m_pSerializer->startElementNS(XML_w, XML_tbl); |
| |
| m_TableFirstCells.push_back(pTableTextNodeInfoInner); |
| m_LastOpenCell.push_back(-1); |
| m_LastClosedCell.push_back(-1); |
| |
| InitTableHelper( pTableTextNodeInfoInner ); |
| TableDefinition( pTableTextNodeInfoInner ); |
| } |
| |
| void DocxAttributeOutput::EndTable() |
| { |
| m_pSerializer->endElementNS( XML_w, XML_tbl ); |
| |
| if ( m_tableReference.m_nTableDepth > 0 ) |
| --m_tableReference.m_nTableDepth; |
| |
| m_LastClosedCell.pop_back(); |
| m_LastOpenCell.pop_back(); |
| m_TableFirstCells.pop_back(); |
| |
| // We closed the table; if it is a nested table, the cell that contains it |
| // still continues |
| // set to true only if we were in a nested table, not otherwise. |
| if( !m_TableFirstCells.empty() ) |
| m_tableReference.m_bTableCellOpen = true; |
| |
| // Cleans the table helper |
| m_xTableWrt.reset(); |
| |
| m_aTableStyleConfs.pop_back(); |
| } |
| |
| void DocxAttributeOutput::StartTableRow( ww8::WW8TableNodeInfoInner::Pointer_t const & pTableTextNodeInfoInner ) |
| { |
| m_pSerializer->startElementNS(XML_w, XML_tr); |
| |
| // Output the row properties |
| m_pSerializer->startElementNS(XML_w, XML_trPr); |
| |
| // Header row: tblHeader |
| const SwTable *pTable = pTableTextNodeInfoInner->getTable( ); |
| if ( pTable->GetRowsToRepeat( ) > pTableTextNodeInfoInner->getRow( ) ) |
| m_pSerializer->singleElementNS(XML_w, XML_tblHeader, FSNS(XML_w, XML_val), "true"); // TODO to overwrite table style may need explicit false |
| |
| TableRowRedline( pTableTextNodeInfoInner ); |
| TableHeight( pTableTextNodeInfoInner ); |
| TableCanSplit( pTableTextNodeInfoInner ); |
| |
| const SwTableBox *pTableBox = pTableTextNodeInfoInner->getTableBox(); |
| const SwTableLine* pTableLine = pTableBox->GetUpper(); |
| if (const SfxGrabBagItem* pItem = pTableLine->GetFrameFormat()->GetAttrSet().GetItem<SfxGrabBagItem>(RES_FRMATR_GRABBAG)) |
| { |
| const std::map<OUString, uno::Any>& rGrabBag = pItem->GetGrabBag(); |
| std::map<OUString, uno::Any>::const_iterator it = rGrabBag.find(u"RowCnfStyle"_ustr); |
| if (it != rGrabBag.end()) |
| { |
| uno::Sequence<beans::PropertyValue> aAttributes = it->second.get< uno::Sequence<beans::PropertyValue> >(); |
| m_pTableStyleExport->CnfStyle(aAttributes); |
| } |
| } |
| |
| |
| m_pSerializer->endElementNS( XML_w, XML_trPr ); |
| } |
| |
| void DocxAttributeOutput::EndTableRow( ) |
| { |
| m_pSerializer->endElementNS( XML_w, XML_tr ); |
| m_LastOpenCell.back() = -1; |
| m_LastClosedCell.back() = -1; |
| } |
| |
| void DocxAttributeOutput::StartTableCell( ww8::WW8TableNodeInfoInner::Pointer_t const & pTableTextNodeInfoInner, sal_uInt32 nCell, sal_uInt32 nRow ) |
| { |
| m_LastOpenCell.back() = nCell; |
| |
| InitTableHelper( pTableTextNodeInfoInner ); |
| |
| // check tracked table column deletion or insertion |
| const SwTableBox* pTabBox = pTableTextNodeInfoInner->getTableBox(); |
| SwRedlineTable::size_type nChange = pTabBox->GetRedline(); |
| if (nChange != SwRedlineTable::npos) |
| m_tableReference.m_bTableCellChanged = true; |
| |
| m_pSerializer->startElementNS(XML_w, XML_tc); |
| |
| // Write the cell properties here |
| TableCellProperties( pTableTextNodeInfoInner, nCell, nRow ); |
| |
| m_tableReference.m_bTableCellOpen = true; |
| } |
| |
| void DocxAttributeOutput::EndTableCell(sal_uInt32 nCell) |
| { |
| m_LastClosedCell.back() = nCell; |
| m_LastOpenCell.back() = -1; |
| |
| if (m_tableReference.m_bTableCellParaSdtOpen) |
| EndParaSdtBlock(); |
| |
| m_pSerializer->endElementNS( XML_w, XML_tc ); |
| |
| m_tableReference.m_bTableCellOpen = false; |
| m_tableReference.m_bTableCellParaSdtOpen = false; |
| m_tableReference.m_bTableCellChanged = false; |
| } |
| |
| void DocxAttributeOutput::StartStyles() |
| { |
| m_pSerializer->startElementNS( XML_w, XML_styles, |
| FSNS( XML_xmlns, XML_w ), GetExport().GetFilter().getNamespaceURL(OOX_NS(doc)), |
| FSNS( XML_xmlns, XML_w14 ), GetExport().GetFilter().getNamespaceURL(OOX_NS(w14)), |
| FSNS( XML_xmlns, XML_mc ), GetExport().GetFilter().getNamespaceURL(OOX_NS(mce)), |
| FSNS( XML_mc, XML_Ignorable ), "w14" ); |
| |
| DocDefaults(); |
| LatentStyles(); |
| } |
| |
| sal_Int32 DocxStringGetToken(DocxStringTokenMap const * pMap, std::u16string_view rName) |
| { |
| OString sName = OUStringToOString(rName, RTL_TEXTENCODING_UTF8); |
| while (pMap->pToken) |
| { |
| if (sName == pMap->pToken) |
| return pMap->nToken; |
| ++pMap; |
| } |
| return 0; |
| } |
| |
| namespace |
| { |
| |
| DocxStringTokenMap const aDefaultTokens[] = { |
| {"defQFormat", XML_defQFormat}, |
| {"defUnhideWhenUsed", XML_defUnhideWhenUsed}, |
| {"defSemiHidden", XML_defSemiHidden}, |
| {"count", XML_count}, |
| {"defUIPriority", XML_defUIPriority}, |
| {"defLockedState", XML_defLockedState}, |
| {nullptr, 0} |
| }; |
| |
| DocxStringTokenMap const aExceptionTokens[] = { |
| {"name", XML_name}, |
| {"locked", XML_locked}, |
| {"uiPriority", XML_uiPriority}, |
| {"semiHidden", XML_semiHidden}, |
| {"unhideWhenUsed", XML_unhideWhenUsed}, |
| {"qFormat", XML_qFormat}, |
| {nullptr, 0} |
| }; |
| |
| } |
| |
| void DocxAttributeOutput::LatentStyles() |
| { |
| // Do we have latent styles available? |
| uno::Reference<beans::XPropertySet> xPropertySet(m_rExport.m_rDoc.GetDocShell()->GetBaseModel(), uno::UNO_QUERY_THROW); |
| uno::Sequence<beans::PropertyValue> aInteropGrabBag; |
| xPropertySet->getPropertyValue(u"InteropGrabBag"_ustr) >>= aInteropGrabBag; |
| uno::Sequence<beans::PropertyValue> aLatentStyles; |
| auto pProp = std::find_if(std::cbegin(aInteropGrabBag), std::cend(aInteropGrabBag), |
| [](const beans::PropertyValue& rProp) { return rProp.Name == "latentStyles"; }); |
| if (pProp != std::cend(aInteropGrabBag)) |
| pProp->Value >>= aLatentStyles; |
| if (!aLatentStyles.hasElements()) |
| return; |
| |
| // Extract default attributes first. |
| rtl::Reference<sax_fastparser::FastAttributeList> pAttributeList = FastSerializerHelper::createAttrList(); |
| uno::Sequence<beans::PropertyValue> aLsdExceptions; |
| for (const auto& rLatentStyle : aLatentStyles) |
| { |
| if (sal_Int32 nToken = DocxStringGetToken(aDefaultTokens, rLatentStyle.Name)) |
| pAttributeList->add(FSNS(XML_w, nToken), rLatentStyle.Value.get<OUString>()); |
| else if (rLatentStyle.Name == "lsdExceptions") |
| rLatentStyle.Value >>= aLsdExceptions; |
| } |
| |
| m_pSerializer->startElementNS(XML_w, XML_latentStyles, detachFrom(pAttributeList)); |
| |
| // Then handle the exceptions. |
| for (const auto& rLsdException : aLsdExceptions) |
| { |
| pAttributeList = FastSerializerHelper::createAttrList(); |
| |
| uno::Sequence<beans::PropertyValue> aAttributes; |
| rLsdException.Value >>= aAttributes; |
| for (const auto& rAttribute : aAttributes) |
| if (sal_Int32 nToken = DocxStringGetToken(aExceptionTokens, rAttribute.Name)) |
| pAttributeList->add(FSNS(XML_w, nToken), rAttribute.Value.get<OUString>()); |
| |
| m_pSerializer->singleElementNS(XML_w, XML_lsdException, detachFrom(pAttributeList)); |
| } |
| |
| m_pSerializer->endElementNS(XML_w, XML_latentStyles); |
| } |
| |
| void DocxAttributeOutput::OutputDefaultItem(const SfxPoolItem& rHt) |
| { |
| bool bMustWrite = true; |
| switch (rHt.Which()) |
| { |
| case RES_CHRATR_CASEMAP: |
| bMustWrite = static_cast< const SvxCaseMapItem& >(rHt).GetCaseMap() != SvxCaseMap::NotMapped; |
| break; |
| case RES_CHRATR_COLOR: |
| bMustWrite = static_cast< const SvxColorItem& >(rHt).GetValue() != COL_AUTO; |
| break; |
| case RES_CHRATR_CONTOUR: |
| bMustWrite = static_cast< const SvxContourItem& >(rHt).GetValue(); |
| break; |
| case RES_CHRATR_CROSSEDOUT: |
| bMustWrite = static_cast< const SvxCrossedOutItem& >(rHt).GetStrikeout() != STRIKEOUT_NONE; |
| break; |
| case RES_CHRATR_ESCAPEMENT: |
| bMustWrite = static_cast< const SvxEscapementItem& >(rHt).GetEscapement() != SvxEscapement::Off; |
| break; |
| case RES_CHRATR_FONT: |
| bMustWrite = true; |
| break; |
| case RES_CHRATR_FONTSIZE: |
| bMustWrite = static_cast< const SvxFontHeightItem& >(rHt).GetHeight() != 200; // see StyleSheetTable_Impl::StyleSheetTable_Impl() where we set this default |
| break; |
| case RES_CHRATR_KERNING: |
| bMustWrite = static_cast< const SvxKerningItem& >(rHt).GetValue() != 0; |
| break; |
| case RES_CHRATR_LANGUAGE: |
| bMustWrite = true; |
| break; |
| case RES_CHRATR_POSTURE: |
| bMustWrite = static_cast< const SvxPostureItem& >(rHt).GetPosture() != ITALIC_NONE; |
| break; |
| case RES_CHRATR_SHADOWED: |
| bMustWrite = static_cast< const SvxShadowedItem& >(rHt).GetValue(); |
| break; |
| case RES_CHRATR_UNDERLINE: |
| bMustWrite = static_cast< const SvxUnderlineItem& >(rHt).GetLineStyle() != LINESTYLE_NONE; |
| break; |
| case RES_CHRATR_WEIGHT: |
| bMustWrite = static_cast< const SvxWeightItem& >(rHt).GetWeight() != WEIGHT_NORMAL; |
| break; |
| case RES_CHRATR_AUTOKERN: |
| bMustWrite = static_cast< const SvxAutoKernItem& >(rHt).GetValue(); |
| break; |
| case RES_CHRATR_BLINK: |
| bMustWrite = static_cast< const SvxBlinkItem& >(rHt).GetValue(); |
| break; |
| case RES_CHRATR_BACKGROUND: |
| { |
| const SvxBrushItem& rBrushItem = static_cast< const SvxBrushItem& >(rHt); |
| bMustWrite = (rBrushItem.GetColor() != COL_AUTO || |
| rBrushItem.GetShadingValue() != ShadingPattern::CLEAR || |
| rBrushItem.GetGraphicObject() != nullptr); |
| } |
| break; |
| |
| case RES_CHRATR_CJK_FONT: |
| bMustWrite = true; |
| break; |
| case RES_CHRATR_CJK_FONTSIZE: |
| bMustWrite = false; // we have written it already as RES_CHRATR_FONTSIZE |
| break; |
| case RES_CHRATR_CJK_LANGUAGE: |
| bMustWrite = true; |
| break; |
| case RES_CHRATR_CJK_POSTURE: |
| bMustWrite = false; // we have written it already as RES_CHRATR_POSTURE |
| break; |
| case RES_CHRATR_CJK_WEIGHT: |
| bMustWrite = false; // we have written it already as RES_CHRATR_WEIGHT |
| break; |
| |
| case RES_CHRATR_CTL_FONT: |
| bMustWrite = true; |
| break; |
| case RES_CHRATR_CTL_FONTSIZE: |
| bMustWrite = static_cast< const SvxFontHeightItem& >(rHt).GetHeight() != 200; // see StyleSheetTable_Impl::StyleSheetTable_Impl() where we set this default |
| break; |
| case RES_CHRATR_CTL_LANGUAGE: |
| bMustWrite = true; |
| break; |
| case RES_CHRATR_CTL_POSTURE: |
| bMustWrite = static_cast< const SvxPostureItem& >(rHt).GetPosture() != ITALIC_NONE; |
| break; |
| case RES_CHRATR_CTL_WEIGHT: |
| bMustWrite = static_cast< const SvxWeightItem& >(rHt).GetWeight() != WEIGHT_NORMAL; |
| break; |
| |
| case RES_CHRATR_ROTATE: |
| bMustWrite = static_cast< const SvxCharRotateItem& >(rHt).GetValue() != 0_deg10; |
| break; |
| case RES_CHRATR_EMPHASIS_MARK: |
| bMustWrite = static_cast< const SvxEmphasisMarkItem& >(rHt).GetEmphasisMark() != FontEmphasisMark::NONE; |
| break; |
| case RES_CHRATR_TWO_LINES: |
| bMustWrite = static_cast< const SvxTwoLinesItem& >(rHt).GetValue(); |
| break; |
| case RES_CHRATR_SCALEW: |
| bMustWrite = static_cast< const SvxCharScaleWidthItem& >(rHt).GetValue() != 100; |
| break; |
| case RES_CHRATR_RELIEF: |
| bMustWrite = static_cast< const SvxCharReliefItem& >(rHt).GetValue() != FontRelief::NONE; |
| break; |
| case RES_CHRATR_HIDDEN: |
| bMustWrite = static_cast< const SvxCharHiddenItem& >(rHt).GetValue(); |
| break; |
| case RES_CHRATR_BOX: |
| { |
| const SvxBoxItem& rBoxItem = static_cast< const SvxBoxItem& >(rHt); |
| bMustWrite = rBoxItem.GetTop() || rBoxItem.GetLeft() || |
| rBoxItem.GetBottom() || rBoxItem.GetRight() || |
| rBoxItem.GetSmallestDistance(); |
| } |
| break; |
| case RES_CHRATR_HIGHLIGHT: |
| { |
| const SvxBrushItem& rBrushItem = static_cast< const SvxBrushItem& >(rHt); |
| bMustWrite = (rBrushItem.GetColor() != COL_AUTO || |
| rBrushItem.GetShadingValue() != ShadingPattern::CLEAR || |
| rBrushItem.GetGraphicObject() != nullptr); |
| } |
| break; |
| |
| case RES_PARATR_LINESPACING: |
| bMustWrite = static_cast< const SvxLineSpacingItem& >(rHt).GetInterLineSpaceRule() != SvxInterLineSpaceRule::Off; |
| break; |
| case RES_PARATR_ADJUST: |
| bMustWrite = static_cast< const SvxAdjustItem& >(rHt).GetAdjust() != SvxAdjust::Left; |
| break; |
| case RES_PARATR_SPLIT: |
| bMustWrite = !static_cast< const SvxFormatSplitItem& >(rHt).GetValue(); |
| break; |
| case RES_PARATR_WIDOWS: |
| bMustWrite = static_cast< const SvxWidowsItem& >(rHt).GetValue(); |
| break; |
| case RES_PARATR_TABSTOP: |
| bMustWrite = static_cast< const SvxTabStopItem& >(rHt).Count() != 0; |
| break; |
| case RES_PARATR_HYPHENZONE: |
| bMustWrite = true; |
| break; |
| case RES_PARATR_NUMRULE: |
| bMustWrite = !static_cast< const SwNumRuleItem& >(rHt).GetValue().isEmpty(); |
| break; |
| case RES_PARATR_SCRIPTSPACE: |
| bMustWrite = !static_cast< const SfxBoolItem& >(rHt).GetValue(); |
| break; |
| case RES_PARATR_HANGINGPUNCTUATION: |
| bMustWrite = !static_cast< const SfxBoolItem& >(rHt).GetValue(); |
| break; |
| case RES_PARATR_FORBIDDEN_RULES: |
| bMustWrite = !static_cast< const SfxBoolItem& >(rHt).GetValue(); |
| break; |
| case RES_PARATR_VERTALIGN: |
| bMustWrite = static_cast< const SvxParaVertAlignItem& >(rHt).GetValue() != SvxParaVertAlignItem::Align::Automatic; |
| break; |
| case RES_PARATR_SNAPTOGRID: |
| bMustWrite = !static_cast< const SvxParaGridItem& >(rHt).GetValue(); |
| break; |
| case RES_CHRATR_GRABBAG: |
| bMustWrite = true; |
| break; |
| |
| default: |
| SAL_INFO("sw.ww8", "Unhandled SfxPoolItem with id " << rHt.Which() ); |
| break; |
| } |
| |
| if (bMustWrite) |
| OutputItem(rHt); |
| } |
| |
| void DocxAttributeOutput::DocDefaults( ) |
| { |
| // Write the '<w:docDefaults>' section here |
| m_pSerializer->startElementNS(XML_w, XML_docDefaults); |
| |
| // Output the default run properties |
| m_pSerializer->startElementNS(XML_w, XML_rPrDefault); |
| |
| StartStyleProperties(false, 0); |
| |
| for (int i = int(RES_CHRATR_BEGIN); i < int(RES_CHRATR_END); ++i) |
| OutputDefaultItem(m_rExport.m_rDoc.GetDefault(i)); |
| |
| EndStyleProperties(false); |
| |
| m_pSerializer->endElementNS(XML_w, XML_rPrDefault); |
| |
| // Output the default paragraph properties |
| m_pSerializer->startElementNS(XML_w, XML_pPrDefault); |
| |
| StartStyleProperties(true, 0); |
| |
| for (int i = int(RES_PARATR_BEGIN); i < int(RES_PARATR_END); ++i) |
| OutputDefaultItem(m_rExport.m_rDoc.GetDefault(i)); |
| |
| EndStyleProperties(true); |
| |
| m_pSerializer->endElementNS(XML_w, XML_pPrDefault); |
| |
| m_pSerializer->endElementNS(XML_w, XML_docDefaults); |
| } |
| |
| void DocxAttributeOutput::EndStyles( sal_uInt16 nNumberOfStyles ) |
| { |
| // HACK |
| // Ms Office seems to have an internal limitation of 4091 styles |
| // and refuses to load .docx with more, even though the spec seems to allow that; |
| // so simply if there are more styles, don't export those |
| const sal_Int32 nCountStylesToWrite = MSWORD_MAX_STYLES_LIMIT - nNumberOfStyles; |
| m_pTableStyleExport->TableStyles(nCountStylesToWrite); |
| m_pSerializer->endElementNS( XML_w, XML_styles ); |
| } |
| |
| void DocxAttributeOutput::DefaultStyle() |
| { |
| // are these the values of enum ww::sti (see ../inc/wwstyles.hxx)? |
| SAL_INFO("sw.ww8", "TODO DocxAttributeOutput::DefaultStyle()"); |
| } |
| |
| /* Writes <a:srcRect> tag back to document.xml if a file contains a cropped image. |
| * NOTE : Tested on images of type JPEG,EMF/WMF,BMP, PNG and GIF. |
| */ |
| void DocxAttributeOutput::WriteSrcRect( |
| const css::uno::Reference<css::beans::XPropertySet>& xShapePropSet, |
| const SwFrameFormat* pFrameFormat) |
| { |
| uno::Reference<graphic::XGraphic> xGraphic; |
| xShapePropSet->getPropertyValue(u"Graphic"_ustr) >>= xGraphic; |
| const Graphic aGraphic(xGraphic); |
| |
| Size aOriginalSize(aGraphic.GetPrefSize()); |
| |
| const MapMode aMap100mm( MapUnit::Map100thMM ); |
| const MapMode& rMapMode = aGraphic.GetPrefMapMode(); |
| if (rMapMode.GetMapUnit() == MapUnit::MapPixel) |
| { |
| aOriginalSize = Application::GetDefaultDevice()->PixelToLogic(aOriginalSize, aMap100mm); |
| } |
| |
| css::text::GraphicCrop aGraphicCropStruct; |
| xShapePropSet->getPropertyValue(u"GraphicCrop"_ustr) >>= aGraphicCropStruct; |
| sal_Int32 nCropL = aGraphicCropStruct.Left; |
| sal_Int32 nCropR = aGraphicCropStruct.Right; |
| sal_Int32 nCropT = aGraphicCropStruct.Top; |
| sal_Int32 nCropB = aGraphicCropStruct.Bottom; |
| |
| // simulate border padding as a negative crop. |
| const SvxBoxItem* pBoxItem; |
| if (pFrameFormat && (pBoxItem = pFrameFormat->GetItemIfSet(RES_BOX, false))) |
| { |
| nCropL -= pBoxItem->GetDistance( SvxBoxItemLine::LEFT ); |
| nCropR -= pBoxItem->GetDistance( SvxBoxItemLine::RIGHT ); |
| nCropT -= pBoxItem->GetDistance( SvxBoxItemLine::TOP ); |
| nCropB -= pBoxItem->GetDistance( SvxBoxItemLine::BOTTOM ); |
| } |
| |
| if (nCropL == 0 && nCropT == 0 && nCropR == 0 && nCropB == 0) |
| return; |
| |
| double widthMultiplier = 100000.0/aOriginalSize.Width(); |
| double heightMultiplier = 100000.0/aOriginalSize.Height(); |
| |
| sal_Int32 left = static_cast<sal_Int32>(rtl::math::round(nCropL * widthMultiplier)); |
| sal_Int32 right = static_cast<sal_Int32>(rtl::math::round(nCropR * widthMultiplier)); |
| sal_Int32 top = static_cast<sal_Int32>(rtl::math::round(nCropT * heightMultiplier)); |
| sal_Int32 bottom = static_cast<sal_Int32>(rtl::math::round(nCropB * heightMultiplier)); |
| |
| m_pSerializer->singleElementNS( XML_a, XML_srcRect, |
| XML_l, OString::number(left), |
| XML_t, OString::number(top), |
| XML_r, OString::number(right), |
| XML_b, OString::number(bottom) ); |
| } |
| |
| uno::Reference<css::text::XTextFrame> DocxAttributeOutput::GetUnoTextFrame( |
| css::uno::Reference<css::drawing::XShape> xShape) |
| { |
| return SwTextBoxHelper::getUnoTextFrame(xShape); |
| } |
| |
| static rtl::Reference<::sax_fastparser::FastAttributeList> CreateDocPrAttrList( |
| DocxExport & rExport, int const nAnchorId, std::u16string_view const& rName, |
| std::u16string_view const& rTitle, std::u16string_view const& rDescription) |
| { |
| rtl::Reference<::sax_fastparser::FastAttributeList> const pAttrs(FastSerializerHelper::createAttrList()); |
| pAttrs->add(XML_id, OString::number(nAnchorId)); |
| pAttrs->add(XML_name, rName); |
| if (rExport.GetFilter().getVersion() != oox::core::ECMA_376_1ST_EDITION) |
| { |
| pAttrs->add(XML_descr, rDescription); |
| pAttrs->add(XML_title, rTitle); |
| } |
| else |
| { // tdf#148952 no title attribute, merge it into descr |
| OUString const value(rTitle.empty() |
| ? OUString(rDescription) |
| : rDescription.empty() |
| ? OUString(rTitle) |
| : OUString::Concat(rTitle) + "\n" + rDescription); |
| pAttrs->add(XML_descr, value); |
| } |
| return pAttrs; |
| } |
| |
| void DocxAttributeOutput::FlyFrameGraphic( const SwGrfNode* pGrfNode, const Size& rSize, const SwFlyFrameFormat* pOLEFrameFormat, SwOLENode* pOLENode, const SdrObject* pSdrObj ) |
| { |
| 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" ); |
| |
| GetSdtEndBefore(pSdrObj); |
| |
| // detect mis-use of the API |
| assert(pGrfNode || (pOLEFrameFormat && pOLENode)); |
| const SwFrameFormat* pFrameFormat = pGrfNode ? pGrfNode->GetFlyFormat() : pOLEFrameFormat; |
| // create the relation ID |
| OString aRelId; |
| OUString sSvgRelId; |
| sal_Int32 nImageType; |
| if ( pGrfNode && pGrfNode->IsLinkedFile() ) |
| { |
| // linked image, just create the relation |
| OUString aFileName; |
| pGrfNode->GetFileFilterNms( &aFileName, nullptr ); |
| |
| sal_Int32 const nFragment(aFileName.indexOf('#')); |
| sal_Int32 const nForbiddenU(aFileName.indexOf("%5C")); |
| sal_Int32 const nForbiddenL(aFileName.indexOf("%5c")); |
| if ( (nForbiddenU != -1 && (nFragment == -1 || nForbiddenU < nFragment)) |
| || (nForbiddenL != -1 && (nFragment == -1 || nForbiddenL < nFragment))) |
| { |
| SAL_WARN("sw.ww8", "DocxAttributeOutput::FlyFrameGraphic: ignoring image with invalid link URL"); |
| return; |
| } |
| |
| // TODO Convert the file name to relative for better interoperability |
| |
| aRelId = m_rExport.AddRelation( |
| oox::getRelationship(Relationship::IMAGE), |
| aFileName ); |
| |
| nImageType = XML_link; |
| } |
| else |
| { |
| // inline, we also have to write the image itself |
| Graphic aGraphic; |
| if (pGrfNode) |
| aGraphic = pGrfNode->GetGrf(); |
| else |
| aGraphic = *pOLENode->GetGraphic(); |
| |
| m_rDrawingML.SetFS(m_pSerializer); // to be sure that we write to the right stream |
| auto pGraphicExport = m_rDrawingML.createGraphicExport(); |
| OUString aImageId = pGraphicExport->writeToStorage(aGraphic, false); |
| aRelId = OUStringToOString(aImageId, RTL_TEXTENCODING_UTF8); |
| |
| if (aGraphic.getVectorGraphicData() && aGraphic.getVectorGraphicData()->getType() == VectorGraphicDataType::Svg) |
| { |
| sSvgRelId = pGraphicExport->writeToStorage(aGraphic, false, drawingml::GraphicExport::TypeHint::SVG); |
| } |
| nImageType = XML_embed; |
| } |
| |
| // In case there are any grab-bag items on the graphic frame, emit them now. |
| // These are always character grab-bags, as graphics are at-char or as-char in Word. |
| if (const SfxGrabBagItem* pGrabBag = pFrameFormat->GetAttrSet().GetItemIfSet(RES_FRMATR_GRABBAG)) |
| { |
| CharGrabBag(*pGrabBag); |
| } |
| |
| rtl::Reference<sax_fastparser::FastAttributeList> xFrameAttributes( |
| FastSerializerHelper::createAttrList()); |
| if (pGrfNode) |
| { |
| const SwAttrSet& rSet = pGrfNode->GetSwAttrSet(); |
| MirrorGraph eMirror = rSet.Get(RES_GRFATR_MIRRORGRF).GetValue(); |
| if (eMirror == MirrorGraph::Vertical || eMirror == MirrorGraph::Both) |
| // Mirror on the vertical axis is a horizontal flip. |
| xFrameAttributes->add(XML_flipH, "1"); |
| // RES_GRFATR_ROTATION is sal_uInt16; use sal_uInt32 for multiplication later |
| if (Degree10 nRot = rSet.Get(RES_GRFATR_ROTATION).GetValue()) |
| { |
| // RES_GRFATR_ROTATION is in 10ths of degree; convert to 100ths for macro |
| sal_uInt32 mOOXMLRot = oox::drawingml::ExportRotateClockwisify(to<Degree100>(nRot)); |
| xFrameAttributes->add(XML_rot, OString::number(mOOXMLRot)); |
| } |
| } |
| |
| css::uno::Reference<css::beans::XPropertySet> xShapePropSet; |
| if (pSdrObj) |
| { |
| css::uno::Reference<css::drawing::XShape> xShape( |
| const_cast<SdrObject*>(pSdrObj)->getUnoShape(), css::uno::UNO_QUERY); |
| xShapePropSet.set(xShape, css::uno::UNO_QUERY); |
| assert(xShapePropSet); |
| } |
| |
| Size aSize = rSize; |
| // We need the original (cropped, but unrotated) size of object. So prefer the object data, |
| // and only use passed frame size as fallback. |
| if (xShapePropSet) |
| { |
| if (css::awt::Size val; xShapePropSet->getPropertyValue(u"Size"_ustr) >>= val) |
| aSize = Size(o3tl::toTwips(val.Width, o3tl::Length::mm100), o3tl::toTwips(val.Height, o3tl::Length::mm100)); |
| } |
| |
| m_rExport.SdrExporter().startDMLAnchorInline(pFrameFormat, aSize); |
| |
| // picture description (used for pic:cNvPr later too) |
| OUString const descr(pGrfNode ? pGrfNode->GetDescription() : pOLEFrameFormat->GetObjDescription()); |
| OUString const title(pGrfNode ? pGrfNode->GetTitle() : pOLEFrameFormat->GetObjTitle()); |
| auto const docPrattrList(CreateDocPrAttrList( |
| GetExport(), m_anchorId++, pFrameFormat->GetName(), title, descr)); |
| m_pSerializer->startElementNS( XML_wp, XML_docPr, docPrattrList ); |
| |
| OUString sURL, sRelId; |
| if (xShapePropSet) |
| { |
| xShapePropSet->getPropertyValue(u"HyperLinkURL"_ustr) >>= sURL; |
| if(!sURL.isEmpty()) |
| { |
| if (sURL.startsWith("#") && sURL.indexOf(' ') != -1 && !sURL.endsWith("|outline") && !sURL.endsWith("|table") && |
| !sURL.endsWith("|frame") && !sURL.endsWith("|graphic") && !sURL.endsWith("|ole") && !sURL.endsWith("|region")) |
| { |
| // Spaces are prohibited in bookmark name. |
| sURL = sURL.replace(' ', '_'); |
| } |
| sRelId = GetExport().GetFilter().addRelation( m_pSerializer->getOutputStream(), |
| oox::getRelationship(Relationship::HYPERLINK), |
| sURL, !sURL.startsWith("#") ); |
| m_pSerializer->singleElementNS( XML_a, XML_hlinkClick, |
| FSNS( XML_xmlns, XML_a ), "http://schemas.openxmlformats.org/drawingml/2006/main", |
| FSNS( XML_r, XML_id ), sRelId); |
| } |
| AddExtLst(m_pSerializer, GetExport(), xShapePropSet); |
| } |
| |
| m_pSerializer->endElementNS( XML_wp, XML_docPr ); |
| |
| m_pSerializer->startElementNS(XML_wp, XML_cNvGraphicFramePr); |
| // TODO change aspect? |
| m_pSerializer->singleElementNS( XML_a, XML_graphicFrameLocks, |
| FSNS( XML_xmlns, XML_a ), GetExport().GetFilter().getNamespaceURL(OOX_NS(dml)), |
| XML_noChangeAspect, "1" ); |
| m_pSerializer->endElementNS( XML_wp, XML_cNvGraphicFramePr ); |
| |
| m_pSerializer->startElementNS( XML_a, XML_graphic, |
| FSNS( XML_xmlns, XML_a ), GetExport().GetFilter().getNamespaceURL(OOX_NS(dml)) ); |
| m_pSerializer->startElementNS( XML_a, XML_graphicData, |
| XML_uri, "http://schemas.openxmlformats.org/drawingml/2006/picture" ); |
| |
| m_pSerializer->startElementNS( XML_pic, XML_pic, |
| FSNS( XML_xmlns, XML_pic ), GetExport().GetFilter().getNamespaceURL(OOX_NS(dmlPicture)) ); |
| |
| m_pSerializer->startElementNS(XML_pic, XML_nvPicPr); |
| // It seems pic:cNvpr and wp:docPr are pretty much the same thing with the same attributes |
| m_pSerializer->startElementNS(XML_pic, XML_cNvPr, docPrattrList); |
| |
| if(!sURL.isEmpty()) |
| m_pSerializer->singleElementNS(XML_a, XML_hlinkClick, FSNS(XML_r, XML_id), sRelId); |
| |
| m_pSerializer->endElementNS( XML_pic, XML_cNvPr ); |
| |
| m_pSerializer->startElementNS(XML_pic, XML_cNvPicPr); |
| // TODO change aspect? |
| m_pSerializer->singleElementNS( XML_a, XML_picLocks, |
| XML_noChangeAspect, "1", XML_noChangeArrowheads, "1" ); |
| m_pSerializer->endElementNS( XML_pic, XML_cNvPicPr ); |
| m_pSerializer->endElementNS( XML_pic, XML_nvPicPr ); |
| |
| // the actual picture |
| m_pSerializer->startElementNS(XML_pic, XML_blipFill); |
| |
| /* At this point we are certain that, WriteImage returns empty RelId |
| for unhandled graphic type. Therefore we write the picture description |
| and not the relation( coz there ain't any), so that the user knows |
| there is an image/graphic in the doc but it is broken instead of |
| completely discarding it. |
| */ |
| if ( aRelId.isEmpty() ) |
| m_pSerializer->startElementNS(XML_a, XML_blip); |
| else |
| m_pSerializer->startElementNS(XML_a, XML_blip, FSNS(XML_r, nImageType), aRelId); |
| |
| const SfxEnumItemInterface* pGrafModeItem = nullptr; |
| if ( pGrfNode && (pGrafModeItem = pGrfNode->GetSwAttrSet().GetItemIfSet(RES_GRFATR_DRAWMODE))) |
| { |
| GraphicDrawMode nMode = static_cast<GraphicDrawMode>(pGrafModeItem->GetEnumValue()); |
| if (nMode == GraphicDrawMode::Greys) |
| m_pSerializer->singleElementNS (XML_a, XML_grayscl); |
| else if (nMode == GraphicDrawMode::Mono) //black/white has a 0,5 threshold in LibreOffice |
| m_pSerializer->singleElementNS (XML_a, XML_biLevel, XML_thresh, OString::number(50000)); |
| else if (nMode == GraphicDrawMode::Watermark) //watermark has a brightness/luminance of 0,5 and contrast of -0.7 in LibreOffice |
| m_pSerializer->singleElementNS( XML_a, XML_lum, XML_bright, OString::number(70000), XML_contrast, OString::number(-70000) ); |
| } |
| |
| if (!sSvgRelId.isEmpty()) |
| { |
| auto pGraphicExport = m_rDrawingML.createGraphicExport(); |
| pGraphicExport->writeSvgExtension(sSvgRelId); |
| } |
| |
| m_pSerializer->endElementNS( XML_a, XML_blip ); |
| |
| if (xShapePropSet) |
| WriteSrcRect(xShapePropSet, pFrameFormat); |
| |
| m_pSerializer->startElementNS(XML_a, XML_stretch); |
| m_pSerializer->singleElementNS(XML_a, XML_fillRect); |
| m_pSerializer->endElementNS( XML_a, XML_stretch ); |
| m_pSerializer->endElementNS( XML_pic, XML_blipFill ); |
| |
| // TODO setup the right values below |
| m_pSerializer->startElementNS(XML_pic, XML_spPr, XML_bwMode, "auto"); |
| |
| m_pSerializer->startElementNS(XML_a, XML_xfrm, xFrameAttributes); |
| |
| m_pSerializer->singleElementNS(XML_a, XML_off, XML_x, "0", XML_y, "0"); |
| OString aWidth( OString::number( TwipsToEMU( aSize.Width() ) ) ); |
| OString aHeight( OString::number( TwipsToEMU( aSize.Height() ) ) ); |
| m_pSerializer->singleElementNS(XML_a, XML_ext, XML_cx, aWidth, XML_cy, aHeight); |
| m_pSerializer->endElementNS( XML_a, XML_xfrm ); |
| m_pSerializer->startElementNS(XML_a, XML_prstGeom, XML_prst, "rect"); |
| m_pSerializer->singleElementNS(XML_a, XML_avLst); |
| m_pSerializer->endElementNS( XML_a, XML_prstGeom ); |
| |
| m_rDrawingML.SetFS(m_pSerializer); // to be sure that we write to the right stream |
| if (xShapePropSet) |
| m_rDrawingML.WriteFill(xShapePropSet, awt::Size(aSize.Width(), aSize.Height())); |
| |
| const SvxBoxItem& rBoxItem = pFrameFormat->GetBox(); |
| const SvxBorderLine* pLeft = rBoxItem.GetLine(SvxBoxItemLine::LEFT); |
| const SvxBorderLine* pRight = rBoxItem.GetLine(SvxBoxItemLine::RIGHT); |
| const SvxBorderLine* pTop = rBoxItem.GetLine(SvxBoxItemLine::TOP); |
| const SvxBorderLine* pBottom = rBoxItem.GetLine(SvxBoxItemLine::BOTTOM); |
| if (pLeft || pRight || pTop || pBottom) |
| m_rExport.SdrExporter().writeBoxItemLine(rBoxItem); |
| |
| m_rExport.SdrExporter().writeDMLEffectLst(*pFrameFormat); |
| |
| m_pSerializer->endElementNS( XML_pic, XML_spPr ); |
| |
| m_pSerializer->endElementNS( XML_pic, XML_pic ); |
| |
| m_pSerializer->endElementNS( XML_a, XML_graphicData ); |
| m_pSerializer->endElementNS( XML_a, XML_graphic ); |
| m_rExport.SdrExporter().endDMLAnchorInline(pFrameFormat); |
| } |
| |
| void DocxAttributeOutput::WriteOLE2Obj( const SdrObject* pSdrObj, SwOLENode& rOLENode, const Size& rSize, const SwFlyFrameFormat* pFlyFrameFormat, const sal_Int8 nFormulaAlignment ) |
| { |
| if( WriteOLEChart( pSdrObj, rSize, pFlyFrameFormat )) |
| return; |
| if( WriteOLEMath( rOLENode , nFormulaAlignment)) |
| return; |
| PostponeOLE( rOLENode, rSize, pFlyFrameFormat ); |
| } |
| |
| bool DocxAttributeOutput::WriteOLEChart( const SdrObject* pSdrObj, const Size& rSize, const SwFlyFrameFormat* pFlyFrameFormat ) |
| { |
| uno::Reference< drawing::XShape > xShape( const_cast<SdrObject*>(pSdrObj)->getUnoShape(), uno::UNO_QUERY ); |
| if (!xShape.is()) |
| return false; |
| |
| uno::Reference<beans::XPropertySet> const xPropSet(xShape, uno::UNO_QUERY); |
| if (!xPropSet.is()) |
| return false; |
| |
| OUString clsid; // why is the property of type string, not sequence<byte>? |
| xPropSet->getPropertyValue(u"CLSID"_ustr) >>= clsid; |
| assert(!clsid.isEmpty()); |
| SvGlobalName aClassID; |
| bool const isValid(aClassID.MakeId(clsid)); |
| assert(isValid); (void)isValid; |
| |
| if (!SotExchange::IsChart(aClassID)) |
| return false; |
| |
| m_aPostponedCharts.push_back(PostponedChart(pSdrObj, rSize, pFlyFrameFormat)); |
| return true; |
| } |
| |
| /* |
| * Write chart hierarchy in w:drawing after end element of w:rPr tag. |
| */ |
| void DocxAttributeOutput::WritePostponedChart() |
| { |
| if (m_aPostponedCharts.empty()) |
| return; |
| |
| for (const PostponedChart& rChart : m_aPostponedCharts) |
| { |
| uno::Reference< chart2::XChartDocument > xChartDoc; |
| uno::Reference< drawing::XShape > xShape(const_cast<SdrObject*>(rChart.object)->getUnoShape(), uno::UNO_QUERY ); |
| if( xShape.is() ) |
| { |
| uno::Reference< beans::XPropertySet > xPropSet( xShape, uno::UNO_QUERY ); |
| if( xPropSet.is() ) |
| xChartDoc.set( xPropSet->getPropertyValue( u"Model"_ustr ), uno::UNO_QUERY ); |
| } |
| |
| if( xChartDoc.is() ) |
| { |
| SAL_INFO("sw.ww8", "DocxAttributeOutput::WriteOLE2Obj: export chart "); |
| |
| m_rExport.SdrExporter().startDMLAnchorInline(rChart.frame, rChart.size); |
| |
| OUString sName(u"Object 1"_ustr); |
| uno::Reference< container::XNamed > xNamed( xShape, uno::UNO_QUERY ); |
| if( xNamed.is() ) |
| sName = xNamed->getName(); |
| |
| // tdf#153203 export a11y related properties |
| uno::Reference<beans::XPropertySet> xShapeProps(xShape, uno::UNO_QUERY); |
| OUString const title(xShapeProps->getPropertyValue(u"Title"_ustr).get<OUString>()); |
| OUString const descr(xShapeProps->getPropertyValue(u"Description"_ustr).get<OUString>()); |
| |
| /* If there is a scenario where a chart is followed by a shape |
| which is being exported as an alternate content then, the |
| docPr Id is being repeated, ECMA 20.4.2.5 says that the |
| docPr Id should be unique, ensuring the same here. |
| */ |
| auto const docPrattrList(CreateDocPrAttrList( |
| GetExport(), m_anchorId++, sName, title, descr)); |
| m_pSerializer->singleElementNS(XML_wp, XML_docPr, docPrattrList); |
| |
| m_pSerializer->singleElementNS(XML_wp, XML_cNvGraphicFramePr); |
| |
| m_pSerializer->startElementNS( XML_a, XML_graphic, |
| FSNS( XML_xmlns, XML_a ), GetExport().GetFilter().getNamespaceURL(OOX_NS(dml)) ); |
| |
| m_pSerializer->startElementNS( XML_a, XML_graphicData, |
| XML_uri, "http://schemas.openxmlformats.org/drawingml/2006/chart" ); |
| |
| OString aRelId; |
| m_nChartCount++; |
| aRelId = m_rExport.OutputChart( xChartDoc, m_nChartCount, m_pSerializer ); |
| |
| m_pSerializer->singleElementNS( XML_c, XML_chart, |
| FSNS( XML_xmlns, XML_c ), GetExport().GetFilter().getNamespaceURL(OOX_NS(dmlChart)), |
| FSNS( XML_xmlns, XML_r ), GetExport().GetFilter().getNamespaceURL(OOX_NS(officeRel)), |
| FSNS( XML_r, XML_id ), aRelId ); |
| |
| m_pSerializer->endElementNS( XML_a, XML_graphicData ); |
| m_pSerializer->endElementNS( XML_a, XML_graphic ); |
| |
| m_rExport.SdrExporter().endDMLAnchorInline(rChart.frame); |
| } |
| } |
| |
| m_aPostponedCharts.clear(); |
| } |
| |
| bool DocxAttributeOutput::WriteOLEMath( const SwOLENode& rOLENode ,const sal_Int8 nAlign) |
| { |
| uno::Reference < embed::XEmbeddedObject > xObj(const_cast<SwOLENode&>(rOLENode).GetOLEObj().GetOleRef()); |
| SvGlobalName aObjName(xObj->getClassID()); |
| |
| if( !SotExchange::IsMath(aObjName) ) |
| return false; |
| |
| try |
| { |
| PostponedMathObjects aPostponedMathObject; |
| aPostponedMathObject.pMathObject = const_cast<SwOLENode*>( &rOLENode); |
| aPostponedMathObject.nMathObjAlignment = nAlign; |
| m_aPostponedMaths.push_back(aPostponedMathObject); |
| } |
| catch (const uno::Exception&) |
| { |
| } |
| return true; |
| } |
| |
| void DocxAttributeOutput::WritePostponedMath(const SwOLENode* pPostponedMath, sal_Int8 nAlign) |
| { |
| uno::Reference < embed::XEmbeddedObject > xObj(const_cast<SwOLENode*>(pPostponedMath)->GetOLEObj().GetOleRef()); |
| if (embed::EmbedStates::LOADED == xObj->getCurrentState()) |
| { |
| // must be running so there is a Component |
| try |
| { |
| xObj->changeState(embed::EmbedStates::RUNNING); |
| } |
| catch (const uno::Exception&) |
| { |
| } |
| } |
| uno::Reference< uno::XInterface > xInterface( xObj->getComponent(), uno::UNO_QUERY ); |
| if (!xInterface.is()) |
| { |
| SAL_WARN("sw.ww8", "Broken math object"); |
| return; |
| } |
| if( oox::FormulaImExportBase* formulaexport = dynamic_cast< oox::FormulaImExportBase* >( xInterface.get())) |
| formulaexport->writeFormulaOoxml( m_pSerializer, GetExport().GetFilter().getVersion(), |
| oox::drawingml::DOCUMENT_DOCX, nAlign); |
| else |
| OSL_FAIL( "Math OLE object cannot write out OOXML" ); |
| } |
| |
| void DocxAttributeOutput::WritePostponedFormControl(const SdrObject* pObject) |
| { |
| if (!pObject || pObject->GetObjInventor() != SdrInventor::FmForm) |
| return; |
| |
| SdrUnoObj *pFormObj = const_cast<SdrUnoObj*>(dynamic_cast< const SdrUnoObj*>(pObject)); |
| if (!pFormObj) |
| return; |
| |
| uno::Reference<awt::XControlModel> xControlModel = pFormObj->GetUnoControlModel(); |
| uno::Reference<lang::XServiceInfo> xInfo(xControlModel, uno::UNO_QUERY); |
| if (!xInfo.is()) |
| return; |
| |
| if (xInfo->supportsService(u"com.sun.star.form.component.DateField"_ustr)) |
| { |
| // gather component properties |
| |
| OUString sDateFormat; |
| uno::Reference<beans::XPropertySet> xPropertySet(xControlModel, uno::UNO_QUERY); |
| |
| OString sDate; |
| OUString aContentText; |
| bool bHasDate = false; |
| css::util::Date aUNODate; |
| if (xPropertySet->getPropertyValue(u"Date"_ustr) >>= aUNODate) |
| { |
| bHasDate = true; |
| Date aDate(aUNODate.Day, aUNODate.Month, aUNODate.Year); |
| sDate = DateToOString(aDate); |
| aContentText = DateToDDMMYYYYOUString(aDate); |
| sDateFormat = "dd/MM/yyyy"; |
| } |
| else |
| { |
| aContentText = xPropertySet->getPropertyValue(u"HelpText"_ustr).get<OUString>(); |
| if(sDateFormat.isEmpty()) |
| sDateFormat = "dd/MM/yyyy"; // Need to set date format even if there is no date set |
| } |
| |
| // output component |
| |
| m_pSerializer->startElementNS(XML_w, XML_sdt); |
| m_pSerializer->startElementNS(XML_w, XML_sdtPr); |
| |
| if (bHasDate) |
| m_pSerializer->startElementNS(XML_w, XML_date, FSNS(XML_w, XML_fullDate), sDate); |
| else |
| m_pSerializer->startElementNS(XML_w, XML_date); |
| |
| m_pSerializer->singleElementNS(XML_w, XML_dateFormat, FSNS(XML_w, XML_val), sDateFormat); |
| m_pSerializer->singleElementNS(XML_w, XML_lid, |
| FSNS(XML_w, XML_val), "en-US"); |
| m_pSerializer->singleElementNS(XML_w, XML_storeMappedDataAs, |
| FSNS(XML_w, XML_val), "dateTime"); |
| m_pSerializer->singleElementNS(XML_w, XML_calendar, |
| FSNS(XML_w, XML_val), "gregorian"); |
| |
| m_pSerializer->endElementNS(XML_w, XML_date); |
| m_pSerializer->endElementNS(XML_w, XML_sdtPr); |
| |
| m_pSerializer->startElementNS(XML_w, XML_sdtContent); |
| m_pSerializer->startElementNS(XML_w, XML_r); |
| |
| RunText(aContentText); |
| m_pSerializer->endElementNS(XML_w, XML_r); |
| m_pSerializer->endElementNS(XML_w, XML_sdtContent); |
| |
| m_pSerializer->endElementNS(XML_w, XML_sdt); |
| } |
| else if (xInfo->supportsService(u"com.sun.star.form.component.ComboBox"_ustr)) |
| { |
| // gather component properties |
| |
| uno::Reference<beans::XPropertySet> xPropertySet(xControlModel, uno::UNO_QUERY); |
| OUString sText = xPropertySet->getPropertyValue(u"Text"_ustr).get<OUString>(); |
| const uno::Sequence<OUString> aItems = xPropertySet->getPropertyValue(u"StringItemList"_ustr).get< uno::Sequence<OUString> >(); |
| |
| // output component |
| |
| m_pSerializer->startElementNS(XML_w, XML_sdt); |
| m_pSerializer->startElementNS(XML_w, XML_sdtPr); |
| |
| m_pSerializer->startElementNS(XML_w, XML_dropDownList); |
| |
| for (const auto& rItem : aItems) |
| { |
| m_pSerializer->singleElementNS(XML_w, XML_listItem, |
| FSNS(XML_w, XML_displayText), rItem, |
| FSNS(XML_w, XML_value), rItem); |
| } |
| |
| m_pSerializer->endElementNS(XML_w, XML_dropDownList); |
| m_pSerializer->endElementNS(XML_w, XML_sdtPr); |
| |
| m_pSerializer->startElementNS(XML_w, XML_sdtContent); |
| m_pSerializer->startElementNS(XML_w, XML_r); |
| RunText(sText); |
| m_pSerializer->endElementNS(XML_w, XML_r); |
| m_pSerializer->endElementNS(XML_w, XML_sdtContent); |
| |
| m_pSerializer->endElementNS(XML_w, XML_sdt); |
| } |
| } |
| |
| void DocxAttributeOutput::WritePostponedActiveXControl(bool bInsideRun) |
| { |
| for( const auto & rPostponedDrawing : m_aPostponedActiveXControls ) |
| { |
| WriteActiveXControl(rPostponedDrawing.object, *rPostponedDrawing.frame, bInsideRun); |
| } |
| m_aPostponedActiveXControls.clear(); |
| } |
| |
| |
| void DocxAttributeOutput::WriteActiveXControl(const SdrObject* pObject, const SwFrameFormat& rFrameFormat, bool bInsideRun) |
| { |
| SdrUnoObj *pFormObj = const_cast<SdrUnoObj*>(dynamic_cast< const SdrUnoObj*>(pObject)); |
| if (!pFormObj) |
| return; |
| |
| uno::Reference<awt::XControlModel> xControlModel = pFormObj->GetUnoControlModel(); |
| if (!xControlModel.is()) |
| return; |
| |
| const bool bAnchoredInline = rFrameFormat.GetAnchor().GetAnchorId() == static_cast<RndStdIds>(css::text::TextContentAnchorType_AS_CHARACTER); |
| |
| if(!bInsideRun) |
| { |
| m_pSerializer->startElementNS(XML_w, XML_r); |
| } |
| |
| // w:pict for floating embedded control and w:object for inline embedded control |
| if(bAnchoredInline) |
| m_pSerializer->startElementNS(XML_w, XML_object); |
| else |
| m_pSerializer->startElementNS(XML_w, XML_pict); |
| |
| // write ActiveX fragment and ActiveX binary |
| uno::Reference<drawing::XShape> xShape(const_cast<SdrObject*>(pObject)->getUnoShape(), uno::UNO_QUERY); |
| std::pair<OString,OString> sRelIdAndName = m_rExport.WriteActiveXObject(xShape, xControlModel); |
| |
| // VML shape definition |
| m_rExport.VMLExporter().SetSkipwzName(true); |
| m_rExport.VMLExporter().SetHashMarkForType(true); |
| m_rExport.VMLExporter().OverrideShapeIDGen(true, "control_shape_"_ostr); |
| OString sShapeId; |
| if(bAnchoredInline) |
| { |
| sShapeId = m_rExport.VMLExporter().AddInlineSdrObject(*pObject, true); |
| } |
| else |
| { |
| SwFormatFollowTextFlow const& rFlow(rFrameFormat.GetFollowTextFlow()); |
| const SwFormatHoriOrient& rHoriOri = rFrameFormat.GetHoriOrient(); |
| const SwFormatVertOrient& rVertOri = rFrameFormat.GetVertOrient(); |
| SwFormatSurround const& rSurround(rFrameFormat.GetSurround()); |
| rtl::Reference<sax_fastparser::FastAttributeList> pAttrList(docx::SurroundToVMLWrap(rSurround)); |
| sShapeId = m_rExport.VMLExporter().AddSdrObject(*pObject, |
| rFlow.GetValue(), |
| rHoriOri.GetHoriOrient(), rVertOri.GetVertOrient(), |
| rHoriOri.GetRelationOrient(), |
| rVertOri.GetRelationOrient(), |
| pAttrList.get(), |
| true); |
| } |
| // Restore default values |
| m_rExport.VMLExporter().SetSkipwzName(false); |
| m_rExport.VMLExporter().SetHashMarkForType(false); |
| m_rExport.VMLExporter().OverrideShapeIDGen(false); |
| |
| // control |
| m_pSerializer->singleElementNS(XML_w, XML_control, |
| FSNS(XML_r, XML_id), sRelIdAndName.first, |
| FSNS(XML_w, XML_name), sRelIdAndName.second, |
| FSNS(XML_w, XML_shapeid), sShapeId); |
| |
| if(bAnchoredInline) |
| m_pSerializer->endElementNS(XML_w, XML_object); |
| else |
| m_pSerializer->endElementNS(XML_w, XML_pict); |
| |
| if(!bInsideRun) |
| { |
| m_pSerializer->endElementNS(XML_w, XML_r); |
| } |
| } |
| |
| bool DocxAttributeOutput::ExportAsActiveXControl(const SdrObject* pObject) const |
| { |
| SdrUnoObj *pFormObj = const_cast<SdrUnoObj*>(dynamic_cast< const SdrUnoObj*>(pObject)); |
| if (!pFormObj) |
| return false; |
| |
| uno::Reference<awt::XControlModel> xControlModel = pFormObj->GetUnoControlModel(); |
| if (!xControlModel.is()) |
| return false; |
| |
| uno::Reference< css::frame::XModel > xModel( m_rExport.m_rDoc.GetDocShell() ? m_rExport.m_rDoc.GetDocShell()->GetModel() : nullptr ); |
| if (!xModel.is()) |
| return false; |
| |
| uno::Reference<lang::XServiceInfo> xInfo(xControlModel, uno::UNO_QUERY); |
| if (!xInfo.is()) |
| return false; |
| |
| // See WritePostponedFormControl |
| // By now date field and combobox is handled on a different way, so let's not interfere with the other method. |
| if(xInfo->supportsService(u"com.sun.star.form.component.DateField"_ustr) || |
| xInfo->supportsService(u"com.sun.star.form.component.ComboBox"_ustr)) |
| return false; |
| |
| oox::ole::OleFormCtrlExportHelper exportHelper(comphelper::getProcessComponentContext(), xModel, xControlModel); |
| return exportHelper.isValid(); |
| } |
| |
| void DocxAttributeOutput::PostponeOLE( SwOLENode& rNode, const Size& rSize, const SwFlyFrameFormat* pFlyFrameFormat ) |
| { |
| if( !m_oPostponedOLEs ) |
| //cannot be postponed, try to write now |
| WriteOLE( rNode, rSize, pFlyFrameFormat ); |
| else |
| m_oPostponedOLEs->push_back( PostponedOLE( &rNode, rSize, pFlyFrameFormat ) ); |
| } |
| |
| /* |
| * Write w:object hierarchy for embedded objects after end element of w:rPr tag. |
| */ |
| void DocxAttributeOutput::WritePostponedOLE() |
| { |
| if( !m_oPostponedOLEs ) |
| return; |
| |
| for( const auto & rPostponedOLE : *m_oPostponedOLEs ) |
| { |
| WriteOLE( *rPostponedOLE.object, rPostponedOLE.size, rPostponedOLE.frame ); |
| } |
| |
| // clear list of postponed objects |
| m_oPostponedOLEs.reset(); |
| } |
| |
| void DocxAttributeOutput::WriteOLE( SwOLENode& rNode, const Size& rSize, const SwFlyFrameFormat* pFlyFrameFormat ) |
| { |
| OSL_ASSERT(pFlyFrameFormat); |
| |
| // get interoperability information about embedded objects |
| uno::Reference< beans::XPropertySet > xPropSet( m_rExport.m_rDoc.GetDocShell()->GetBaseModel(), uno::UNO_QUERY_THROW ); |
| uno::Sequence< beans::PropertyValue > aGrabBag, aObjectsInteropList,aObjectInteropAttributes; |
| xPropSet->getPropertyValue( UNO_NAME_MISC_OBJ_INTEROPGRABBAG ) >>= aGrabBag; |
| auto pProp = std::find_if(std::cbegin(aGrabBag), std::cend(aGrabBag), |
| [](const beans::PropertyValue& rProp) { return rProp.Name == "EmbeddedObjects"; }); |
| if (pProp != std::cend(aGrabBag)) |
| pProp->Value >>= aObjectsInteropList; |
| |
| SwOLEObj& aObject = rNode.GetOLEObj(); |
| uno::Reference < embed::XEmbeddedObject > xObj( aObject.GetOleRef() ); |
| comphelper::EmbeddedObjectContainer* aContainer = aObject.GetObject().GetContainer(); |
| OUString sObjectName = aContainer->GetEmbeddedObjectName( xObj ); |
| |
| // set some attributes according to the type of the embedded object |
| OUString sProgID, sDrawAspect; |
| switch (rNode.GetAspect()) |
| { |
| case embed::Aspects::MSOLE_CONTENT: sDrawAspect = "Content"; break; |
| case embed::Aspects::MSOLE_DOCPRINT: sDrawAspect = "DocPrint"; break; |
| case embed::Aspects::MSOLE_ICON: sDrawAspect = "Icon"; break; |
| case embed::Aspects::MSOLE_THUMBNAIL: sDrawAspect = "Thumbnail"; break; |
| default: |
| SAL_WARN("sw.ww8", "DocxAttributeOutput::WriteOLE: invalid aspect value"); |
| } |
| auto pObjectsInterop = std::find_if(std::cbegin(aObjectsInteropList), std::cend(aObjectsInteropList), |
| [&sObjectName](const beans::PropertyValue& rProp) { return rProp.Name == sObjectName; }); |
| if (pObjectsInterop != std::cend(aObjectsInteropList)) |
| pObjectsInterop->Value >>= aObjectInteropAttributes; |
| |
| for (const auto& rObjectInteropAttribute : aObjectInteropAttributes) |
| { |
| if ( rObjectInteropAttribute.Name == "ProgID" ) |
| { |
| rObjectInteropAttribute.Value >>= sProgID; |
| } |
| } |
| |
| // write embedded file |
| OString sId = m_rExport.WriteOLEObject(aObject, sProgID); |
| |
| if( sId.isEmpty() ) |
| { |
| // the embedded file could not be saved |
| // fallback: save as an image |
| FlyFrameGraphic( nullptr, rSize, pFlyFrameFormat, &rNode ); |
| return; |
| } |
| |
| // write preview image |
| const Graphic* pGraphic = rNode.GetGraphic(); |
| m_rDrawingML.SetFS(m_pSerializer); |
| OUString sImageId = m_rDrawingML.writeGraphicToStorage(*pGraphic); |
| |
| if ( sDrawAspect == "Content" ) |
| { |
| try |
| { |
| awt::Size aSize = xObj->getVisualAreaSize( rNode.GetAspect() ); |
| |
| MapUnit aUnit = VCLUnoHelper::UnoEmbed2VCLMapUnit( xObj->getMapUnit( rNode.GetAspect() ) ); |
| Size aOriginalSize( OutputDevice::LogicToLogic(Size( aSize.Width, aSize.Height), |
| MapMode(aUnit), MapMode(MapUnit::MapTwip))); |
| |
| m_pSerializer->startElementNS( XML_w, XML_object, |
| FSNS(XML_w, XML_dxaOrig), OString::number(aOriginalSize.Width()), |
| FSNS(XML_w, XML_dyaOrig), OString::number(aOriginalSize.Height()) ); |
| } |
| catch ( uno::Exception& ) |
| { |
| m_pSerializer->startElementNS(XML_w, XML_object); |
| } |
| } |
| else |
| { |
| m_pSerializer->startElementNS(XML_w, XML_object); |
| } |
| |
| OString sShapeId = "ole_" + sId; |
| |
| //OLE Shape definition |
| WriteOLEShape(*pFlyFrameFormat, rSize, sShapeId, sImageId); |
| |
| //OLE Object definition |
| m_pSerializer->singleElementNS(XML_o, XML_OLEObject, |
| XML_Type, "Embed", |
| XML_ProgID, sProgID, |
| XML_ShapeID, sShapeId, |
| XML_DrawAspect, sDrawAspect, |
| XML_ObjectID, "_" + OString::number(comphelper::rng::uniform_int_distribution(0, std::numeric_limits<int>::max())), |
| FSNS( XML_r, XML_id ), sId ); |
| |
| m_pSerializer->endElementNS(XML_w, XML_object); |
| } |
| |
| void DocxAttributeOutput::WriteOLEShape(const SwFlyFrameFormat& rFrameFormat, const Size& rSize, |
| std::string_view rShapeId, const OUString& rImageId) |
| { |
| assert(m_pSerializer); |
| |
| //Here is an attribute list where we collect the attributes what we want to export |
| rtl::Reference<FastAttributeList> pAttr = FastSerializerHelper::createAttrList(); |
| pAttr->add(XML_id, rShapeId); |
| |
| //export the fixed shape type for picture frame |
| m_pSerializer->write(vml::VMLExport::GetVMLShapeTypeDefinition(rShapeId, true)); |
| pAttr->add(XML_type, OString::Concat("_x0000_t") + rShapeId); |
| |
| //Export the style attribute for position and size |
| pAttr->add(XML_style, GetOLEStyle(rFrameFormat, rSize)); |
| //Get the OLE frame |
| const SvxBoxItem& rBox = rFrameFormat.GetAttrSet().GetBox(); |
| OString sLineType; |
| OString sDashType; |
| //Word does not handle differently the four sides, |
| //so we have to choose, and the left one is the winner: |
| if (rBox.GetLeft()) |
| { |
| //Get the left border color and width |
| const Color aLineColor = rBox.GetLeft()->GetColor(); |
| const tools::Long aLineWidth = rBox.GetLeft()->GetWidth(); |
| |
| //Convert the left OLE border style to OOXML |
| //FIXME improve if it's necessary |
| switch (rBox.GetLeft()->GetBorderLineStyle()) |
| { |
| case SvxBorderLineStyle::SOLID: |
| sLineType = "Single"_ostr; |
| sDashType = "Solid"_ostr; |
| break; |
| case SvxBorderLineStyle::DASHED: |
| sLineType = "Single"_ostr; |
| sDashType = "Dash"_ostr; |
| break; |
| case SvxBorderLineStyle::DASH_DOT: |
| sLineType = "Single"_ostr; |
| sDashType = "DashDot"_ostr; |
| break; |
| case SvxBorderLineStyle::DASH_DOT_DOT: |
| sLineType = "Single"_ostr; |
| sDashType = "ShortDashDotDot"_ostr; |
| break; |
| case SvxBorderLineStyle::DOTTED: |
| sLineType = "Single"_ostr; |
| sDashType = "Dot"_ostr; |
| break; |
| case SvxBorderLineStyle::DOUBLE: |
| sLineType = "ThinThin"_ostr; |
| sDashType = "Solid"_ostr; |
| break; |
| case SvxBorderLineStyle::DOUBLE_THIN: |
| sLineType = "ThinThin"_ostr; |
| sDashType = "Solid"_ostr; |
| break; |
| case SvxBorderLineStyle::EMBOSSED: |
| sLineType = "Single"_ostr; |
| sDashType = "Solid"_ostr; |
| break; |
| case SvxBorderLineStyle::ENGRAVED: |
| sLineType = "Single"_ostr; |
| sDashType = "Solid"_ostr; |
| break; |
| case SvxBorderLineStyle::FINE_DASHED: |
| sLineType = "Single"_ostr; |
| sDashType = "Dot"_ostr; |
| break; |
| case SvxBorderLineStyle::INSET: |
| sLineType = "Single"_ostr; |
| sDashType = "Solid"_ostr; |
| break; |
| case SvxBorderLineStyle::OUTSET: |
| sLineType = "Single"_ostr; |
| sDashType = "Solid"_ostr; |
| break; |
| case SvxBorderLineStyle::THICKTHIN_LARGEGAP: |
| case SvxBorderLineStyle::THICKTHIN_MEDIUMGAP: |
| case SvxBorderLineStyle::THICKTHIN_SMALLGAP: |
| sLineType = "ThickThin"_ostr; |
| sDashType = "Solid"_ostr; |
| break; |
| case SvxBorderLineStyle::THINTHICK_LARGEGAP: |
| case SvxBorderLineStyle::THINTHICK_MEDIUMGAP: |
| case SvxBorderLineStyle::THINTHICK_SMALLGAP: |
| sLineType = "ThinThick"_ostr; |
| sDashType = "Solid"_ostr; |
| break; |
| case SvxBorderLineStyle::NONE: |
| sLineType = ""_ostr; |
| sDashType = ""_ostr; |
| break; |
| default: |
| SAL_WARN("sw.ww8", "Unknown line type on OOXML ELE export!"); |
| break; |
| } |
| |
| //If there is a line add it for export |
| if (!sLineType.isEmpty() && !sDashType.isEmpty()) |
| { |
| pAttr->add(XML_stroked, "t"); |
| pAttr->add(XML_strokecolor, "#" + msfilter::util::ConvertColor(aLineColor)); |
| pAttr->add(XML_strokeweight, OString::number(aLineWidth / 20) + "pt"); |
| } |
| } |
| |
| //Let's check the filltype of the OLE |
| switch (rFrameFormat.GetAttrSet().Get(XATTR_FILLSTYLE).GetValue()) |
| { |
| case drawing::FillStyle::FillStyle_SOLID: |
| { |
| //If solid, we get the color and add it to the exporter |
| const Color rShapeColor = rFrameFormat.GetAttrSet().Get(XATTR_FILLCOLOR).GetColorValue(); |
| pAttr->add(XML_filled, "t"); |
| pAttr->add(XML_fillcolor, "#" + msfilter::util::ConvertColor(rShapeColor)); |
| break; |
| } |
| case drawing::FillStyle::FillStyle_GRADIENT: |
| case drawing::FillStyle::FillStyle_HATCH: |
| case drawing::FillStyle::FillStyle_BITMAP: |
| //TODO |
| break; |
| case drawing::FillStyle::FillStyle_NONE: |
| { |
| pAttr->add(XML_filled, "f"); |
| break; |
| } |
| default: |
| SAL_WARN("sw.ww8", "Unknown fill type on OOXML OLE export!"); |
| break; |
| } |
| pAttr->addNS(XML_o, XML_ole, ""); //compulsory, even if it's empty |
| m_pSerializer->startElementNS(XML_v, XML_shape, pAttr);//Write the collected attrs... |
| |
| if (!sLineType.isEmpty() && !sDashType.isEmpty()) //If there is a line/dash style it is time to export it |
| { |
| m_pSerializer->singleElementNS(XML_v, XML_stroke, XML_linestyle, sLineType, XML_dashstyle, sDashType); |
| } |
| |
| // shape filled with the preview image |
| m_pSerializer->singleElementNS(XML_v, XML_imagedata, |
| FSNS(XML_r, XML_id), rImageId, |
| FSNS(XML_o, XML_title), ""); |
| |
| //export wrap settings |
| if (rFrameFormat.GetAnchor().GetAnchorId() != RndStdIds::FLY_AS_CHAR) //As-char objs does not have surround. |
| ExportOLESurround(rFrameFormat.GetSurround()); |
| |
| m_pSerializer->endElementNS(XML_v, XML_shape); |
| } |
| |
| OString DocxAttributeOutput::GetOLEStyle(const SwFlyFrameFormat& rFormat, const Size& rSize) |
| { |
| //tdf#131539: Export OLE positions in docx: |
| //This string will store the position output for the xml |
| OString aPos; |
| //This string will store the relative position for aPos |
| OString aAnch; |
| |
| if (rFormat.GetAnchor().GetAnchorId() != RndStdIds::FLY_AS_CHAR) |
| { |
| //Get the horizontal alignment of the OLE via the frame format, to aHAlign |
| OString aHAlign = convertToOOXMLHoriOrient(rFormat.GetHoriOrient().GetHoriOrient(), |
| rFormat.GetHoriOrient().IsPosToggle()); |
| //Get the vertical alignment of the OLE via the frame format to aVAlign |
| OString aVAlign = convertToOOXMLVertOrient(rFormat.GetVertOrient().GetVertOrient()); |
| |
| // Check if the OLE anchored to page: |
| const bool bIsPageAnchor = rFormat.GetAnchor().GetAnchorId() == RndStdIds::FLY_AT_PAGE; |
| |
| //Get the relative horizontal positions for the anchors |
| OString aHAnch |
| = bIsPageAnchor |
| ? "page"_ostr |
| : convertToOOXMLHoriOrientRel(rFormat.GetHoriOrient().GetRelationOrient()); |
| //Get the relative vertical positions for the anchors |
| OString aVAnch = convertToOOXMLVertOrientRel(rFormat.GetVertOrient().GetRelationOrient()); |
| |
| //Choice that the horizontal position is relative or not |
| if (!aHAlign.isEmpty()) |
| aHAlign = ";mso-position-horizontal:" + aHAlign; |
| aHAlign = ";mso-position-horizontal-relative:" + aHAnch; |
| |
| //Choice that the vertical position is relative or not |
| if (!aVAlign.isEmpty()) |
| aVAlign = ";mso-position-vertical:" + aVAlign; |
| aVAlign = ";mso-position-vertical-relative:" + aVAnch; |
| |
| //Set the anchoring information into one string for aPos |
| aAnch = aHAlign + aVAlign; |
| |
| //Query the positions to aPos from frameformat |
| aPos = |
| "position:absolute;margin-left:" + OString::number(double(rFormat.GetHoriOrient().GetPos()) / 20) + |
| "pt;margin-top:" + OString::number(double(rFormat.GetVertOrient().GetPos()) / 20) + "pt;"; |
| } |
| |
| OString sShapeStyle = "width:" + OString::number( double( rSize.Width() ) / 20 ) + |
| "pt;height:" + OString::number( double( rSize.Height() ) / 20 ) + |
| "pt"; //from VMLExport::AddRectangleDimensions(), it does: value/20 |
| |
| const SvxLRSpaceItem& rLRSpace = rFormat.GetLRSpace(); |
| if (rLRSpace.IsExplicitZeroMarginValLeft() || rLRSpace.GetLeft()) |
| sShapeStyle += ";mso-wrap-distance-left:" + OString::number(double(rLRSpace.GetLeft()) / 20) + "pt"; |
| if (rLRSpace.IsExplicitZeroMarginValRight() || rLRSpace.GetRight()) |
| sShapeStyle += ";mso-wrap-distance-right:" + OString::number(double(rLRSpace.GetRight()) / 20) + "pt"; |
| const SvxULSpaceItem& rULSpace = rFormat.GetULSpace(); |
| if (rULSpace.GetUpper()) |
| sShapeStyle += ";mso-wrap-distance-top:" + OString::number(double(rULSpace.GetUpper()) / 20) + "pt"; |
| if (rULSpace.GetLower()) |
| sShapeStyle += ";mso-wrap-distance-bottom:" + OString::number(double(rULSpace.GetLower()) / 20) + "pt"; |
| |
| //Export anchor setting, if it exists |
| if (!aPos.isEmpty() && !aAnch.isEmpty()) |
| sShapeStyle = aPos + sShapeStyle + aAnch; |
| |
| return sShapeStyle; |
| } |
| |
| void DocxAttributeOutput::ExportOLESurround(const SwFormatSurround& rWrap) |
| { |
| const bool bIsContour = rWrap.IsContour(); //Has the shape contour or not |
| OString sSurround; |
| OString sSide; |
| |
| //Map the ODF wrap settings to OOXML one |
| switch (rWrap.GetSurround()) |
| { |
| case text::WrapTextMode::WrapTextMode_NONE: |
| sSurround = "topAndBottom"_ostr; |
| break; |
| case text::WrapTextMode::WrapTextMode_PARALLEL: |
| sSurround = bIsContour ? "tight"_ostr : "square"_ostr; |
| break; |
| case text::WrapTextMode::WrapTextMode_DYNAMIC: |
| sSide = "largest"_ostr; |
| sSurround = bIsContour ? "tight"_ostr : "square"_ostr; |
| break; |
| case text::WrapTextMode::WrapTextMode_LEFT: |
| sSide = "left"_ostr; |
| sSurround = bIsContour ? "tight"_ostr : "square"_ostr; |
| break; |
| case text::WrapTextMode::WrapTextMode_RIGHT: |
| sSide = "right"_ostr; |
| sSurround = bIsContour ? "tight"_ostr : "square"_ostr; |
| break; |
| default: |
| SAL_WARN("sw.ww8", "Unknown surround type on OOXML export!"); |
| break; |
| } |
| |
| //if there is a setting export it: |
| if (!sSurround.isEmpty()) |
| { |
| if (sSide.isEmpty()) |
| m_pSerializer->singleElementNS(XML_w10, XML_wrap, XML_type, sSurround); |
| else |
| m_pSerializer->singleElementNS(XML_w10, XML_wrap, XML_type, sSurround, XML_side, sSide); |
| } |
| } |
| |
| void DocxAttributeOutput::WritePostponedCustomShape() |
| { |
| if (!m_oPostponedCustomShape) |
| return; |
| |
| for( const auto & rPostponedDrawing : *m_oPostponedCustomShape) |
| { |
| m_rExport.GetFilter().SetMaxDocId(m_anchorId + 1); |
| if ( IsAlternateContentChoiceOpen() ) |
| m_rExport.SdrExporter().writeDMLDrawing(rPostponedDrawing.object, rPostponedDrawing.frame, m_anchorId++); |
| else |
| m_rExport.SdrExporter().writeDMLAndVMLDrawing(rPostponedDrawing.object, *rPostponedDrawing.frame, m_anchorId++); |
| m_anchorId = m_rExport.GetFilter().GetMaxDocId(); |
| } |
| m_oPostponedCustomShape.reset(); |
| } |
| |
| void DocxAttributeOutput::WritePostponedDMLDrawing() |
| { |
| if (!m_oPostponedDMLDrawings) |
| return; |
| |
| // Clear the list early, this method may be called recursively. |
| std::optional< std::vector<PostponedDrawing> > pPostponedDMLDrawings(std::move(m_oPostponedDMLDrawings)); |
| std::optional< std::vector<PostponedOLE> > pPostponedOLEs(std::move(m_oPostponedOLEs)); |
| m_oPostponedDMLDrawings.reset(); |
| m_oPostponedOLEs.reset(); |
| |
| for( const auto & rPostponedDrawing : *pPostponedDMLDrawings ) |
| { |
| // Avoid w:drawing within another w:drawing. |
| m_rExport.GetFilter().SetMaxDocId(m_anchorId + 1); |
| if ( IsAlternateContentChoiceOpen() && !( m_rExport.SdrExporter().IsDrawingOpen()) ) |
| m_rExport.SdrExporter().writeDMLDrawing(rPostponedDrawing.object, rPostponedDrawing.frame, m_anchorId++); |
| else |
| m_rExport.SdrExporter().writeDMLAndVMLDrawing(rPostponedDrawing.object, *rPostponedDrawing.frame, m_anchorId++); |
| m_anchorId = m_rExport.GetFilter().GetMaxDocId(); |
| } |
| |
| m_oPostponedOLEs = std::move(pPostponedOLEs); |
| } |
| |
| void DocxAttributeOutput::WriteFlyFrame(const ww8::Frame& rFrame) |
| { |
| m_pSerializer->mark(Tag_OutputFlyFrame); |
| |
| switch ( rFrame.GetWriterType() ) |
| { |
| case ww8::Frame::eGraphic: |
| { |
| const SdrObject* pSdrObj = rFrame.GetFrameFormat().FindRealSdrObject(); |
| const SwNode *pNode = rFrame.GetContent(); |
| const SwGrfNode *pGrfNode = pNode ? pNode->GetGrfNode() : nullptr; |
| if ( pGrfNode ) |
| { |
| if (!m_oPostponedGraphic) |
| { |
| m_bPostponedProcessingFly = false ; |
| FlyFrameGraphic( pGrfNode, rFrame.GetLayoutSize(), nullptr, nullptr, pSdrObj); |
| } |
| else // we are writing out attributes, but w:drawing should not be inside w:rPr, |
| { // so write it out later |
| m_bPostponedProcessingFly = true ; |
| m_oPostponedGraphic->push_back(PostponedGraphic(pGrfNode, rFrame.GetLayoutSize(), pSdrObj)); |
| } |
| } |
| } |
| break; |
| case ww8::Frame::eDrawing: |
| { |
| const SdrObject* pSdrObj = rFrame.GetFrameFormat().FindRealSdrObject(); |
| if ( pSdrObj ) |
| { |
| const bool bIsDiagram(nullptr != pSdrObj && pSdrObj->isDiagram()); |
| |
| if (bIsDiagram) |
| { |
| if ( !m_oPostponedDiagrams ) |
| { |
| m_bPostponedProcessingFly = false ; |
| m_rExport.SdrExporter().writeDiagram( pSdrObj, rFrame.GetFrameFormat(), m_anchorId++); |
| } |
| else // we are writing out attributes, but w:drawing should not be inside w:rPr, |
| { // so write it out later |
| m_bPostponedProcessingFly = true ; |
| m_oPostponedDiagrams->push_back( PostponedDiagram( pSdrObj, &(rFrame.GetFrameFormat()) )); |
| } |
| } |
| else |
| { |
| if (!m_oPostponedDMLDrawings) |
| { |
| m_rExport.GetFilter().SetMaxDocId(m_anchorId + 1); |
| if ( IsAlternateContentChoiceOpen() ) |
| { |
| // Do not write w:drawing inside w:drawing. Instead Postpone the Inner Drawing. |
| if( m_rExport.SdrExporter().IsDrawingOpen() ) |
| m_oPostponedCustomShape->push_back(PostponedDrawing(pSdrObj, &(rFrame.GetFrameFormat()))); |
| else |
| m_rExport.SdrExporter().writeDMLDrawing( pSdrObj, &rFrame.GetFrameFormat(), m_anchorId++); |
| } |
| else |
| m_rExport.SdrExporter().writeDMLAndVMLDrawing( pSdrObj, rFrame.GetFrameFormat(), m_anchorId++); |
| m_anchorId = m_rExport.GetFilter().GetMaxDocId(); |
| |
| m_bPostponedProcessingFly = false ; |
| } |
| // IsAlternateContentChoiceOpen(): check is to ensure that only one object is getting added. Without this check, plus one object gets added |
| // m_bParagraphFrameOpen: check if the frame is open. |
| else if (IsAlternateContentChoiceOpen() && m_bParagraphFrameOpen) |
| m_oPostponedCustomShape->push_back(PostponedDrawing(pSdrObj, &(rFrame.GetFrameFormat()))); |
| else |
| { |
| // we are writing out attributes, but w:drawing should not be inside w:rPr, so write it out later |
| m_bPostponedProcessingFly = true ; |
| m_oPostponedDMLDrawings->push_back(PostponedDrawing(pSdrObj, &(rFrame.GetFrameFormat()))); |
| } |
| } |
| } |
| } |
| break; |
| case ww8::Frame::eTextBox: |
| { |
| // If this is a TextBox of a shape, then ignore: it's handled in WriteTextBox(). |
| if (DocxSdrExport::isTextBox(rFrame.GetFrameFormat())) |
| break; |
| |
| // If this is a TextBox containing a table which we already exported directly, ignore it |
| if (m_aFloatingTablesOfParagraph.find(&rFrame.GetFrameFormat()) != m_aFloatingTablesOfParagraph.end()) |
| break; |
| |
| // The frame output is postponed to the end of the anchor paragraph |
| bool bDuplicate = false; |
| const OUString& rName = rFrame.GetFrameFormat().GetName(); |
| if (m_aFramesOfParagraph.size() && !rName.isEmpty()) |
| { |
| const unsigned nSize = m_aFramesOfParagraph.top().size(); |
| for (unsigned nIndex = 0; nIndex < nSize; ++nIndex) |
| { |
| const OUString& rNameExisting |
| = m_aFramesOfParagraph.top()[nIndex].GetFrameFormat().GetName(); |
| |
| if (rName == rNameExisting) |
| { |
| bDuplicate = true; |
| break; |
| } |
| } |
| } |
| |
| if( !bDuplicate ) |
| { |
| m_bPostponedProcessingFly = true ; |
| if ( m_aFramesOfParagraph.size() ) |
| m_aFramesOfParagraph.top().emplace_back(rFrame); |
| } |
| } |
| break; |
| case ww8::Frame::eOle: |
| { |
| const SwFrameFormat &rFrameFormat = rFrame.GetFrameFormat(); |
| const SdrObject *pSdrObj = rFrameFormat.FindRealSdrObject(); |
| if ( pSdrObj ) |
| { |
| SwNodeIndex aIdx(*rFrameFormat.GetContent().GetContentIdx(), 1); |
| SwOLENode& rOLENd = *aIdx.GetNode().GetOLENode(); |
| |
| //output variable for the formula alignment (default inline) |
| sal_Int8 nAlign(FormulaImExportBase::eFormulaAlign::INLINE); |
| auto xObj(rOLENd.GetOLEObj().GetOleRef()); //get the xObject of the formula |
| |
| //tdf133030: Export formula position |
| //If we have a formula with inline anchor... |
| if(SotExchange::IsMath(xObj->getClassID()) && rFrame.IsInline()) |
| { |
| SwNode const* const pAnchorNode = rFrameFormat.GetAnchor().GetAnchorNode(); |
| if(pAnchorNode) |
| { |
| //Get the text node what the formula anchored to |
| const SwTextNode* pTextNode = pAnchorNode->GetTextNode(); |
| if(pTextNode && pTextNode->Len() == 1) |
| { |
| //Get the paragraph alignment |
| auto aParaAdjust = pTextNode->GetSwAttrSet().GetAdjust().GetAdjust(); |
| //And set the formula according to the paragraph alignment |
| if (aParaAdjust == SvxAdjust::Center) |
| nAlign = FormulaImExportBase::eFormulaAlign::CENTER; |
| else if (aParaAdjust == SvxAdjust::Right) |
| nAlign = FormulaImExportBase::eFormulaAlign::RIGHT; |
| else // left in the case of left and justified paragraph alignments |
| nAlign = FormulaImExportBase::eFormulaAlign::LEFT; |
| } |
| } |
| } |
| WriteOLE2Obj( pSdrObj, rOLENd, rFrame.GetLayoutSize(), dynamic_cast<const SwFlyFrameFormat*>( &rFrameFormat ), nAlign); |
| m_bPostponedProcessingFly = false ; |
| } |
| } |
| break; |
| case ww8::Frame::eFormControl: |
| { |
| const SdrObject* pObject = rFrame.GetFrameFormat().FindRealSdrObject(); |
| if(ExportAsActiveXControl(pObject)) |
| m_aPostponedActiveXControls.emplace_back(pObject, &(rFrame.GetFrameFormat())); |
| else |
| m_aPostponedFormControls.push_back(pObject); |
| m_bPostponedProcessingFly = true ; |
| } |
| break; |
| default: |
| SAL_INFO("sw.ww8", "TODO DocxAttributeOutput::OutputFlyFrame_Impl( const ww8::Frame& rFrame ) - frame type " << |
| ( rFrame.GetWriterType() == ww8::Frame::eTextBox ? "eTextBox": |
| ( rFrame.GetWriterType() == ww8::Frame::eOle ? "eOle": "???" ) ) ); |
| break; |
| } |
| |
| m_pSerializer->mergeTopMarks(Tag_OutputFlyFrame); |
| } |
| |
| void DocxAttributeOutput::OutputFlyFrame_Impl(const ww8::Frame& rFrame, const Point& /*rNdTopLeft*/) |
| { |
| /// The old OutputFlyFrame_Impl() moved to WriteFlyFrame(). |
| /// Now if a frame anchored inside another frame, it will |
| /// not be exported immediately, because OOXML does not |
| /// support that feature, instead it postponed and exported |
| /// later when the original shape closed. |
| |
| if (rFrame.IsInline()) |
| { |
| m_nEmbedFlyLevel++; |
| WriteFlyFrame(rFrame); |
| m_nEmbedFlyLevel--; |
| return; |
| } |
| |
| if (m_nEmbedFlyLevel == 0) |
| { |
| if (m_vPostponedFlys.empty()) |
| { |
| m_nEmbedFlyLevel++; |
| WriteFlyFrame(rFrame); |
| m_nEmbedFlyLevel--; |
| } |
| else |
| for (auto it = m_vPostponedFlys.begin(); it != m_vPostponedFlys.end();) |
| { |
| m_nEmbedFlyLevel++; |
| WriteFlyFrame(*it); |
| it = m_vPostponedFlys.erase(it); |
| m_nEmbedFlyLevel--; |
| } |
| } |
| else |
| { |
| bool bFound = false; |
| for (const auto& i : m_vPostponedFlys) |
| { |
| if (i.RefersToSameFrameAs(rFrame)) |
| { |
| bFound = true; |
| break; |
| } |
| } |
| if (!bFound) |
| { |
| if (auto pParentFly = rFrame.GetContentNode()->GetFlyFormat()) |
| { |
| auto aHori(rFrame.GetFrameFormat().GetHoriOrient()); |
| aHori.SetPos(aHori.GetPos() + pParentFly->GetHoriOrient().GetPos()); |
| auto aVori(rFrame.GetFrameFormat().GetVertOrient()); |
| aVori.SetPos(aVori.GetPos() + pParentFly->GetVertOrient().GetPos()); |
| |
| const_cast<SwFrameFormat&>(rFrame.GetFrameFormat()).SetFormatAttr(aHori); |
| const_cast<SwFrameFormat&>(rFrame.GetFrameFormat()).SetFormatAttr(aVori); |
| const_cast<SwFrameFormat&>(rFrame.GetFrameFormat()).SetFormatAttr(pParentFly->GetAnchor()); |
| |
| m_vPostponedFlys.push_back(rFrame); |
| } |
| |
| } |
| } |
| } |
| |
| void DocxAttributeOutput::WriteOutliner(const OutlinerParaObject& rParaObj) |
| { |
| const EditTextObject& rEditObj = rParaObj.GetTextObject(); |
| MSWord_SdrAttrIter aAttrIter( m_rExport, rEditObj, TXT_HFTXTBOX ); |
| |
| sal_Int32 nPara = rEditObj.GetParagraphCount(); |
| |
| m_pSerializer->startElementNS(XML_w, XML_txbxContent); |
| for (sal_Int32 n = 0; n < nPara; ++n) |
| { |
| if( n ) |
| aAttrIter.NextPara( n ); |
| |
| OUString aStr( rEditObj.GetText( n )); |
| sal_Int32 nCurrentPos = 0; |
| sal_Int32 nEnd = aStr.getLength(); |
| |
| StartParagraph(ww8::WW8TableNodeInfo::Pointer_t(), false); |
| |
| // Write paragraph properties. |
| StartParagraphProperties(); |
| aAttrIter.OutParaAttr(false); |
| SfxItemSet aParagraphMarkerProperties(m_rExport.m_rDoc.GetAttrPool()); |
| EndParagraphProperties(aParagraphMarkerProperties, nullptr, nullptr, nullptr); |
| |
| do { |
| const sal_Int32 nNextAttr = std::min(aAttrIter.WhereNext(), nEnd); |
| |
| m_pSerializer->startElementNS(XML_w, XML_r); |
| |
| // Write run properties. |
| m_pSerializer->startElementNS(XML_w, XML_rPr); |
| aAttrIter.OutAttr(nCurrentPos); |
| WriteCollectedRunProperties(); |
| m_pSerializer->endElementNS(XML_w, XML_rPr); |
| |
| bool bTextAtr = aAttrIter.IsTextAttr( nCurrentPos ); |
| if( !bTextAtr ) |
| { |
| OUString aOut( aStr.copy( nCurrentPos, nNextAttr - nCurrentPos ) ); |
| RunText(aOut); |
| } |
| |
| if ( !m_sRawText.isEmpty() ) |
| { |
| RunText( m_sRawText ); |
| m_sRawText.clear(); |
| } |
| |
| m_pSerializer->endElementNS( XML_w, XML_r ); |
| |
| nCurrentPos = nNextAttr; |
| aAttrIter.NextPos(); |
| } |
| while( nCurrentPos < nEnd ); |
| EndParagraph(ww8::WW8TableNodeInfoInner::Pointer_t()); |
| } |
| m_pSerializer->endElementNS( XML_w, XML_txbxContent ); |
| } |
| |
| void DocxAttributeOutput::pushToTableExportContext(DocxTableExportContext& rContext) |
| { |
| rContext.m_pTableInfo = m_rExport.m_pTableInfo; |
| m_rExport.m_pTableInfo = std::make_shared<ww8::WW8TableInfo>(); |
| |
| rContext.m_bTableCellOpen = m_tableReference.m_bTableCellOpen; |
| m_tableReference.m_bTableCellOpen = false; |
| |
| rContext.m_nTableDepth = m_tableReference.m_nTableDepth; |
| m_tableReference.m_nTableDepth = 0; |
| |
| rContext.m_bStartedParaSdt = m_aParagraphSdt.m_bStartedSdt; |
| m_aParagraphSdt.m_bStartedSdt = false; |
| rContext.m_bStartedRunSdt = m_aRunSdt.m_bStartedSdt; |
| m_aRunSdt.m_bStartedSdt = false; |
| |
| rContext.m_nHyperLinkCount = m_nHyperLinkCount.back(); |
| m_nHyperLinkCount.back() = 0; |
| } |
| |
| void DocxAttributeOutput::popFromTableExportContext(DocxTableExportContext const & rContext) |
| { |
| m_rExport.m_pTableInfo = rContext.m_pTableInfo; |
| m_tableReference.m_bTableCellOpen = rContext.m_bTableCellOpen; |
| m_tableReference.m_nTableDepth = rContext.m_nTableDepth; |
| m_aParagraphSdt.m_bStartedSdt = rContext.m_bStartedParaSdt; |
| m_aRunSdt.m_bStartedSdt = rContext.m_bStartedRunSdt; |
| m_nHyperLinkCount.back() = rContext.m_nHyperLinkCount; |
| } |
| |
| void DocxAttributeOutput::WriteTextBox(uno::Reference<drawing::XShape> xShape) |
| { |
| DocxTableExportContext aTableExportContext(*this); |
| |
| SwFrameFormat* pTextBox = SwTextBoxHelper::getOtherTextBoxFormat(xShape); |
| assert(pTextBox); |
| const SwPosition* pAnchor = nullptr; |
| const bool bFlyAtPage = pTextBox->GetAnchor().GetAnchorId() == RndStdIds::FLY_AT_PAGE; |
| if (bFlyAtPage) //tdf135711 |
| { |
| auto pNdIdx = pTextBox->GetContent().GetContentIdx(); |
| if (pNdIdx) //Is that possible it is null? |
| pAnchor = new SwPosition(*pNdIdx); |
| } |
| else |
| { |
| pAnchor = pTextBox->GetAnchor().GetContentAnchor();//This might be null |
| } |
| |
| if (pAnchor) //pAnchor can be null, so that's why not assert here. |
| { |
| ww8::Frame aFrame(*pTextBox, *pAnchor); |
| m_rExport.SdrExporter().writeDMLTextFrame(&aFrame, m_anchorId++, /*bTextBoxOnly=*/true); |
| if (bFlyAtPage) |
| { |
| delete pAnchor; |
| } |
| } |
| } |
| |
| void DocxAttributeOutput::WriteVMLTextBox(uno::Reference<drawing::XShape> xShape) |
| { |
| DocxTableExportContext aTableExportContext(*this); |
| |
| SwFrameFormat* pTextBox = SwTextBoxHelper::getOtherTextBoxFormat(xShape); |
| assert(pTextBox); |
| const SwPosition* pAnchor = nullptr; |
| if (pTextBox->GetAnchor().GetAnchorId() == RndStdIds::FLY_AT_PAGE) //tdf135711 |
| { |
| auto pNdIdx = pTextBox->GetContent().GetContentIdx(); |
| if (pNdIdx) //Is that possible it is null? |
| pAnchor = new SwPosition(*pNdIdx); |
| } |
| else |
| { |
| pAnchor = pTextBox->GetAnchor().GetContentAnchor();//This might be null |
| } |
| |
| if (pAnchor) //pAnchor can be null, so that's why not assert here. |
| { |
| ww8::Frame aFrame(*pTextBox, *pAnchor); |
| m_rExport.SdrExporter().writeVMLTextFrame(&aFrame, /*bTextBoxOnly=*/true); |
| if (pTextBox->GetAnchor().GetAnchorId() == RndStdIds::FLY_AT_PAGE) |
| { |
| delete pAnchor; |
| } |
| } |
| } |
| |
| oox::drawingml::DrawingML& DocxAttributeOutput::GetDrawingML() |
| { |
| return m_rDrawingML; |
| } |
| |
| bool DocxAttributeOutput::MaybeOutputBrushItem(SfxItemSet const& rSet) |
| { |
| const XFillStyleItem* pXFillStyleItem(rSet.GetItem<XFillStyleItem>(XATTR_FILLSTYLE)); |
| |
| if ((pXFillStyleItem && pXFillStyleItem->GetValue() != drawing::FillStyle_NONE) |
| || !m_rExport.SdrExporter().getDMLTextFrameSyntax()) |
| { |
| return false; |
| } |
| |
| // sw text frames are opaque by default, even with fill none! |
| std::unique_ptr<SfxItemSet> const pClone(rSet.Clone()); |
| XFillColorItem const aColor(OUString(), COL_WHITE); |
| pClone->Put(aColor); |
| // call getSvxBrushItemForSolid - this also takes XFillTransparenceItem into account |
| XFillStyleItem const aSolid(drawing::FillStyle_SOLID); |
| pClone->Put(aSolid); |
| std::unique_ptr<SvxBrushItem> const pBrush(getSvxBrushItemFromSourceSet(*pClone, RES_BACKGROUND)); |
| FormatBackground(*pBrush); |
| return true; |
| } |
| |
| namespace { |
| |
| /// Functor to do case-insensitive ordering of OUString instances. |
| struct OUStringIgnoreCase |
| { |
| bool operator() (std::u16string_view lhs, std::u16string_view rhs) const |
| { |
| return o3tl::compareToIgnoreAsciiCase(lhs, rhs) < 0; |
| } |
| }; |
| |
| } |
| |
| /// Guesses if a style created in Writer (no grab-bag) should be qFormat or not. |
| static bool lcl_guessQFormat(const OUString& rName, sal_uInt16 nWwId) |
| { |
| // If the style has no dedicated STI number, then it's probably a custom style -> qFormat. |
| if (nWwId == ww::stiUser) |
| return true; |
| |
| // Allow exported built-in styles UI language neutral |
| if ( nWwId == ww::stiNormal || |
| ( nWwId>= ww::stiLev1 && nWwId <= ww::stiLev9 ) || |
| nWwId == ww::stiCaption || nWwId == ww::stiTitle || |
| nWwId == ww::stiSubtitle || nWwId == ww::stiStrong || |
| nWwId == ww::stiEmphasis ) |
| return true; |
| |
| static o3tl::sorted_vector<OUString, OUStringIgnoreCase> const aAllowlist |
| { |
| u"No Spacing"_ustr, |
| u"List Paragraph"_ustr, |
| u"Quote"_ustr, |
| u"Intense Quote"_ustr, |
| u"Subtle Emphasis"_ustr, |
| u"Intense Emphasis"_ustr, |
| u"Subtle Reference"_ustr, |
| u"Intense Reference"_ustr, |
| u"Book Title"_ustr, |
| u"TOC Heading"_ustr, |
| }; |
| // Not custom style? Then we have a list of standard styles which should be qFormat. |
| return aAllowlist.find(rName) != aAllowlist.end(); |
| } |
| |
| void DocxAttributeOutput::StartStyle( const OUString& rName, StyleType eType, |
| sal_uInt16 nBase, sal_uInt16 nNext, sal_uInt16 nLink, sal_uInt16 nWwId, sal_uInt16 nSlot, bool bAutoUpdate ) |
| { |
| bool bQFormat = false, bUnhideWhenUsed = false, bSemiHidden = false, bLocked = false, bDefault = false, bCustomStyle = false; |
| OUString aRsid, aUiPriority; |
| rtl::Reference<FastAttributeList> pStyleAttributeList = FastSerializerHelper::createAttrList(); |
| uno::Any aAny; |
| if (eType == STYLE_TYPE_PARA || eType == STYLE_TYPE_CHAR) |
| { |
| const SwFormat* pFormat = m_rExport.m_pStyles->GetSwFormat(nSlot); |
| pFormat->GetGrabBagItem(aAny); |
| } |
| else |
| { |
| const SwNumRule* pRule = m_rExport.m_pStyles->GetSwNumRule(nSlot); |
| pRule->GetGrabBagItem(aAny); |
| } |
| const uno::Sequence<beans::PropertyValue>& rGrabBag = aAny.get< uno::Sequence<beans::PropertyValue> >(); |
| |
| for (const auto& rProp : rGrabBag) |
| { |
| if (rProp.Name == "uiPriority") |
| aUiPriority = rProp.Value.get<OUString>(); |
| else if (rProp.Name == "qFormat") |
| bQFormat = true; |
| else if (rProp.Name == "rsid") |
| aRsid = rProp.Value.get<OUString>(); |
| else if (rProp.Name == "unhideWhenUsed") |
| bUnhideWhenUsed = true; |
| else if (rProp.Name == "semiHidden") |
| bSemiHidden = true; |
| else if (rProp.Name == "locked") |
| bLocked = true; |
| else if (rProp.Name == "default") |
| bDefault = rProp.Value.get<bool>(); |
| else if (rProp.Name == "customStyle") |
| bCustomStyle = rProp.Value.get<bool>(); |
| else |
| SAL_WARN("sw.ww8", "Unhandled style property: " << rProp.Name); |
| } |
| |
| const char* pType = nullptr; |
| switch (eType) |
| { |
| case STYLE_TYPE_PARA: |
| pType = "paragraph"; |
| break; |
| case STYLE_TYPE_CHAR: |
| pType = "character"; |
| break; |
| case STYLE_TYPE_LIST: pType = "numbering"; break; |
| } |
| pStyleAttributeList->add(FSNS( XML_w, XML_type ), pType); |
| pStyleAttributeList->add(FSNS(XML_w, XML_styleId), m_rExport.m_pStyles->GetStyleId(nSlot)); |
| if (bDefault) |
| pStyleAttributeList->add(FSNS(XML_w, XML_default), "1"); |
| if (bCustomStyle) |
| pStyleAttributeList->add(FSNS(XML_w, XML_customStyle), "1"); |
| m_pSerializer->startElementNS( XML_w, XML_style, pStyleAttributeList); |
| m_pSerializer->singleElementNS(XML_w, XML_name, FSNS(XML_w, XML_val), rName); |
| |
| if ( nBase != 0x0FFF && eType != STYLE_TYPE_LIST) |
| { |
| m_pSerializer->singleElementNS( XML_w, XML_basedOn, |
| FSNS( XML_w, XML_val ), m_rExport.m_pStyles->GetStyleId(nBase) ); |
| } |
| |
| if ( nNext != nSlot && eType != STYLE_TYPE_LIST) |
| { |
| m_pSerializer->singleElementNS( XML_w, XML_next, |
| FSNS( XML_w, XML_val ), m_rExport.m_pStyles->GetStyleId(nNext) ); |
| } |
| |
| if (nLink != 0x0FFF && (eType == STYLE_TYPE_PARA || eType == STYLE_TYPE_CHAR)) |
| { |
| m_pSerializer->singleElementNS(XML_w, XML_link, FSNS(XML_w, XML_val), |
| m_rExport.m_pStyles->GetStyleId(nLink)); |
| } |
| |
| if ( bAutoUpdate ) |
| m_pSerializer->singleElementNS(XML_w, XML_autoRedefine); |
| |
| if (!aUiPriority.isEmpty()) |
| m_pSerializer->singleElementNS(XML_w, XML_uiPriority, FSNS(XML_w, XML_val), aUiPriority); |
| if (bSemiHidden) |
| m_pSerializer->singleElementNS(XML_w, XML_semiHidden); |
| if (bUnhideWhenUsed) |
| m_pSerializer->singleElementNS(XML_w, XML_unhideWhenUsed); |
| |
| if (bQFormat || lcl_guessQFormat(rName, nWwId)) |
| m_pSerializer->singleElementNS(XML_w, XML_qFormat); |
| if (bLocked) |
| m_pSerializer->singleElementNS(XML_w, XML_locked); |
| if (!aRsid.isEmpty()) |
| m_pSerializer->singleElementNS(XML_w, XML_rsid, FSNS(XML_w, XML_val), aRsid); |
| } |
| |
| void DocxAttributeOutput::EndStyle() |
| { |
| m_pSerializer->endElementNS( XML_w, XML_style ); |
| } |
| |
| void DocxAttributeOutput::StartStyleProperties( bool bParProp, sal_uInt16 /*nStyle*/ ) |
| { |
| if ( bParProp ) |
| { |
| m_pSerializer->startElementNS(XML_w, XML_pPr); |
| InitCollectedParagraphProperties(); |
| } |
| else |
| { |
| m_pSerializer->startElementNS(XML_w, XML_rPr); |
| InitCollectedRunProperties(); |
| } |
| } |
| |
| void DocxAttributeOutput::EndStyleProperties( bool bParProp ) |
| { |
| if ( bParProp ) |
| { |
| WriteCollectedParagraphProperties(); |
| |
| // Merge the marks for the ordered elements |
| m_pSerializer->mergeTopMarks(Tag_InitCollectedParagraphProperties); |
| |
| m_pSerializer->endElementNS( XML_w, XML_pPr ); |
| } |
| else |
| { |
| WriteCollectedRunProperties(); |
| |
| // Merge the marks for the ordered elements |
| m_pSerializer->mergeTopMarks(Tag_InitCollectedRunProperties); |
| |
| m_pSerializer->endElementNS( XML_w, XML_rPr ); |
| } |
| } |
| |
| void DocxAttributeOutput::OutlineNumbering(sal_uInt8 const /*nLvl*/) |
| { |
| // Handled by ParaOutlineLevel() instead. |
| } |
| |
| void DocxAttributeOutput::ParaOutlineLevel(const SfxUInt16Item& rItem) |
| { |
| sal_uInt16 nOutLvl = std::min(rItem.GetValue(), sal_uInt16(WW8ListManager::nMaxLevel)); |
| // Outline Level: in LO Body Text = 0, in MS Body Text = 9 |
| nOutLvl = nOutLvl ? nOutLvl - 1 : 9; |
| m_pSerializer->singleElementNS(XML_w, XML_outlineLvl, FSNS(XML_w, XML_val), OString::number(nOutLvl)); |
| } |
| |
| void DocxAttributeOutput::PageBreakBefore( bool bBreak ) |
| { |
| if ( bBreak ) |
| m_pSerializer->singleElementNS(XML_w, XML_pageBreakBefore); |
| else |
| m_pSerializer->singleElementNS( XML_w, XML_pageBreakBefore, |
| FSNS( XML_w, XML_val ), "false" ); |
| } |
| |
| void DocxAttributeOutput::SectionBreak( sal_uInt8 nC, bool bBreakAfter, const WW8_SepInfo* pSectionInfo, bool bExtraPageBreak) |
| { |
| switch ( nC ) |
| { |
| case msword::ColumnBreak: |
| // The column break should be output in the next paragraph... |
| if ( m_nColBreakStatus == COLBRK_WRITE ) |
| m_nColBreakStatus = COLBRK_WRITEANDPOSTPONE; |
| else |
| m_nColBreakStatus = COLBRK_POSTPONE; |
| break; |
| case msword::PageBreak: |
| if ( pSectionInfo ) |
| { |
| // Detect when the current node is the last node in the |
| // document: the last section is written explicitly in |
| // DocxExport::WriteMainText(), don't duplicate that here. |
| SwNodeIndex aCurrentNode(m_rExport.m_pCurPam->GetPointNode()); |
| SwNodeIndex aLastNode(m_rExport.m_rDoc.GetNodes().GetEndOfContent(), -1); |
| bool bEmit = aCurrentNode != aLastNode; |
| |
| if (!bEmit) |
| { |
| // Need to still emit an empty section at the end of the |
| // document in case balanced columns are wanted, since the last |
| // section in Word is always balanced. |
| sal_uInt16 nColumns = 1; |
| bool bBalance = false; |
| if (const SwSectionFormat* pFormat = pSectionInfo->pSectionFormat) |
| { |
| if (pFormat != reinterpret_cast<SwSectionFormat*>(sal_IntPtr(-1))) |
| { |
| nColumns = pFormat->GetCol().GetNumCols(); |
| const SwFormatNoBalancedColumns& rNoBalanced = pFormat->GetBalancedColumns(); |
| bBalance = !rNoBalanced.GetValue(); |
| } |
| } |
| bEmit = (nColumns > 1 && bBalance); |
| } |
| |
| // don't add section properties if this will be the first |
| // paragraph in the document |
| if ( !m_bParagraphOpened && !m_bIsFirstParagraph && bEmit ) |
| { |
| // Create a dummy paragraph if needed |
| m_pSerializer->startElementNS(XML_w, XML_p); |
| m_pSerializer->startElementNS(XML_w, XML_pPr); |
| |
| m_rExport.SectionProperties( *pSectionInfo ); |
| |
| m_pSerializer->endElementNS( XML_w, XML_pPr ); |
| if (bExtraPageBreak) |
| { |
| m_pSerializer->startElementNS(XML_w, XML_r); |
| m_pSerializer->singleElementNS(XML_w, XML_br, FSNS(XML_w, XML_type), "page"); |
| m_pSerializer->endElementNS(XML_w, XML_r); |
| } |
| m_pSerializer->endElementNS( XML_w, XML_p ); |
| } |
| else |
| { |
| if (bExtraPageBreak && m_bParagraphOpened) |
| { |
| m_pSerializer->startElementNS(XML_w, XML_r); |
| m_pSerializer->singleElementNS(XML_w, XML_br, FSNS(XML_w, XML_type), "page"); |
| m_pSerializer->endElementNS(XML_w, XML_r); |
| } |
| // postpone the output of this; it has to be done inside the |
| // paragraph properties, so remember it until then |
| m_pSectionInfo.reset( new WW8_SepInfo( *pSectionInfo )); |
| } |
| } |
| else if ( m_bParagraphOpened ) |
| { |
| if (bBreakAfter) |
| // tdf#128889 |
| m_bPageBreakAfter = true; |
| else |
| { |
| m_pSerializer->startElementNS(XML_w, XML_r); |
| m_pSerializer->singleElementNS(XML_w, XML_br, FSNS(XML_w, XML_type), "page"); |
| m_pSerializer->endElementNS(XML_w, XML_r); |
| } |
| } |
| else |
| m_bPostponedPageBreak = true; |
| |
| break; |
| default: |
| SAL_INFO("sw.ww8", "Unknown section break to write: " << nC ); |
| break; |
| } |
| } |
| |
| void DocxAttributeOutput::EndParaSdtBlock() |
| { |
| if (m_aParagraphSdt.m_bStartedSdt) |
| { |
| // Paragraph-level SDT still open? Close it now. |
| m_aParagraphSdt.EndSdtBlock(m_pSerializer); |
| } |
| } |
| |
| void DocxAttributeOutput::StartSection() |
| { |
| m_pSerializer->startElementNS(XML_w, XML_sectPr); |
| m_bOpenedSectPr = true; |
| |
| // Write the elements in the spec order |
| static const sal_Int32 aOrder[] = |
| { |
| FSNS( XML_w, XML_headerReference ), |
| FSNS( XML_w, XML_footerReference ), |
| FSNS( XML_w, XML_footnotePr ), |
| FSNS( XML_w, XML_endnotePr ), |
| FSNS( XML_w, XML_type ), |
| FSNS( XML_w, XML_pgSz ), |
| FSNS( XML_w, XML_pgMar ), |
| FSNS( XML_w, XML_paperSrc ), |
| FSNS( XML_w, XML_pgBorders ), |
| FSNS( XML_w, XML_lnNumType ), |
| FSNS( XML_w, XML_pgNumType ), |
| FSNS( XML_w, XML_cols ), |
| FSNS( XML_w, XML_formProt ), |
| FSNS( XML_w, XML_vAlign ), |
| FSNS( XML_w, XML_noEndnote ), |
| FSNS( XML_w, XML_titlePg ), |
| FSNS( XML_w, XML_textDirection ), |
| FSNS( XML_w, XML_bidi ), |
| FSNS( XML_w, XML_rtlGutter ), |
| FSNS( XML_w, XML_docGrid ), |
| FSNS( XML_w, XML_printerSettings ), |
| FSNS( XML_w, XML_sectPrChange ) |
| }; |
| |
| // postpone the output so that we can later [in EndParagraphProperties()] |
| // prepend the properties before the run |
| // coverity[overrun-buffer-arg : FALSE] - coverity has difficulty with css::uno::Sequence |
| m_pSerializer->mark(Tag_StartSection, comphelper::containerToSequence(aOrder)); |
| m_bHadSectPr = true; |
| } |
| |
| void DocxAttributeOutput::EndSection() |
| { |
| // Write the section properties |
| if ( m_pSectionSpacingAttrList.is() ) |
| { |
| m_pSerializer->singleElementNS( XML_w, XML_pgMar, detachFrom( m_pSectionSpacingAttrList ) ); |
| } |
| |
| // Order the elements |
| m_pSerializer->mergeTopMarks(Tag_StartSection); |
| |
| m_pSerializer->endElementNS( XML_w, XML_sectPr ); |
| m_bOpenedSectPr = false; |
| } |
| |
| void DocxAttributeOutput::SectionFormProtection( bool bProtected ) |
| { |
| if ( bProtected ) |
| m_pSerializer->singleElementNS(XML_w, XML_formProt, FSNS(XML_w, XML_val), "true"); |
| else |
| m_pSerializer->singleElementNS(XML_w, XML_formProt, FSNS(XML_w, XML_val), "false"); |
| } |
| |
| void DocxAttributeOutput::SectionRtlGutter(const SfxBoolItem& rRtlGutter) |
| { |
| if (!rRtlGutter.GetValue()) |
| { |
| return; |
| } |
| |
| m_pSerializer->singleElementNS(XML_w, XML_rtlGutter); |
| } |
| |
| void DocxAttributeOutput::TextLineBreak(const SwFormatLineBreak& rLineBreak) |
| { |
| m_oLineBreakClear = rLineBreak.GetValue(); |
| } |
| |
| void DocxAttributeOutput::WriteLineBreak() |
| { |
| if (!m_oLineBreakClear.has_value()) |
| { |
| return; |
| } |
| |
| rtl::Reference<FastAttributeList> pAttr = FastSerializerHelper::createAttrList(); |
| pAttr->add(FSNS(XML_w, XML_type), "textWrapping"); |
| switch (*m_oLineBreakClear) |
| { |
| case SwLineBreakClear::NONE: |
| pAttr->add(FSNS(XML_w, XML_clear), "none"); |
| break; |
| case SwLineBreakClear::LEFT: |
| pAttr->add(FSNS(XML_w, XML_clear), "left"); |
| break; |
| case SwLineBreakClear::RIGHT: |
| pAttr->add(FSNS(XML_w, XML_clear), "right"); |
| break; |
| case SwLineBreakClear::ALL: |
| pAttr->add(FSNS(XML_w, XML_clear), "all"); |
| break; |
| } |
| m_oLineBreakClear.reset(); |
| |
| m_pSerializer->singleElementNS(XML_w, XML_br, pAttr); |
| } |
| |
| void DocxAttributeOutput::SectionLineNumbering( sal_uLong nRestartNo, const SwLineNumberInfo& rLnNumInfo ) |
| { |
| rtl::Reference<FastAttributeList> pAttr = FastSerializerHelper::createAttrList(); |
| pAttr->add( FSNS( XML_w, XML_countBy ), OString::number(rLnNumInfo.GetCountBy())); |
| pAttr->add( FSNS( XML_w, XML_restart ), rLnNumInfo.IsRestartEachPage() ? "newPage" : "continuous" ); |
| if( rLnNumInfo.GetPosFromLeft()) |
| pAttr->add( FSNS( XML_w, XML_distance ), OString::number(rLnNumInfo.GetPosFromLeft())); |
| if (nRestartNo > 0) |
| // Writer is 1-based, Word is 0-based. |
| pAttr->add(FSNS(XML_w, XML_start), OString::number(nRestartNo - 1)); |
| m_pSerializer->singleElementNS( XML_w, XML_lnNumType, pAttr ); |
| } |
| |
| void DocxAttributeOutput::SectionTitlePage() |
| { |
| m_pSerializer->singleElementNS(XML_w, XML_titlePg); |
| } |
| |
| void DocxAttributeOutput::SectionPageBorders( const SwFrameFormat* pFormat, const SwFrameFormat* /*pFirstPageFormat*/ ) |
| { |
| // Output the margins |
| |
| const SvxBoxItem& rBox = pFormat->GetBox( ); |
| |
| const SvxBorderLine* pLeft = rBox.GetLeft( ); |
| const SvxBorderLine* pTop = rBox.GetTop( ); |
| const SvxBorderLine* pRight = rBox.GetRight( ); |
| const SvxBorderLine* pBottom = rBox.GetBottom( ); |
| |
| if ( !(pBottom || pTop || pLeft || pRight) ) |
| return; |
| |
| OutputBorderOptions aOutputBorderOptions = lcl_getBoxBorderOptions(); |
| |
| // Check if there is a shadow item |
| const SfxPoolItem* pItem = GetExport().HasItem( RES_SHADOW ); |
| if ( pItem ) |
| { |
| const SvxShadowItem* pShadowItem = static_cast<const SvxShadowItem*>(pItem); |
| aOutputBorderOptions.aShadowLocation = pShadowItem->GetLocation(); |
| } |
| |
| // By top margin, impl_borders() means the distance between the top of the page and the header frame. |
| editeng::WordPageMargins aMargins = m_pageMargins; |
| HdFtDistanceGlue aGlue(pFormat->GetAttrSet()); |
| if (aGlue.HasHeader()) |
| aMargins.nTop = aGlue.m_DyaHdrTop; |
| // Ditto for bottom margin. |
| if (aGlue.HasFooter()) |
| aMargins.nBottom = aGlue.m_DyaHdrBottom; |
| |
| if (pFormat->GetDoc()->getIDocumentSettingAccess().get(DocumentSettingId::GUTTER_AT_TOP)) |
| { |
| aMargins.nTop += pFormat->GetLRSpace().GetGutterMargin(); |
| } |
| else |
| { |
| aMargins.nLeft += pFormat->GetLRSpace().GetGutterMargin(); |
| } |
| |
| aOutputBorderOptions.pDistances = std::make_shared<editeng::WordBorderDistances>(); |
| editeng::BorderDistancesToWord(rBox, aMargins, *aOutputBorderOptions.pDistances); |
| |
| // All distances are relative to the text margins |
| m_pSerializer->startElementNS(XML_w, XML_pgBorders, |
| FSNS(XML_w, XML_display), "allPages", |
| FSNS(XML_w, XML_offsetFrom), aOutputBorderOptions.pDistances->bFromEdge ? "page" : "text"); |
| |
| std::map<SvxBoxItemLine, css::table::BorderLine2> aEmptyMap; // empty styles map |
| impl_borders( m_pSerializer, rBox, aOutputBorderOptions, aEmptyMap ); |
| |
| m_pSerializer->endElementNS( XML_w, XML_pgBorders ); |
| |
| } |
| |
| void DocxAttributeOutput::SectionBiDi( bool bBiDi ) |
| { |
| if ( bBiDi ) |
| m_pSerializer->singleElementNS(XML_w, XML_bidi); |
| } |
| |
| // Converting Numbering Format Code to string |
| static OString lcl_ConvertNumberingType(sal_Int16 nNumberingType, const SfxItemSet* pOutSet, OString& rFormat, const OString& sDefault = ""_ostr ) |
| { |
| OString aType = sDefault; |
| |
| switch ( nNumberingType ) |
| { |
| case SVX_NUM_CHARS_UPPER_LETTER: |
| case SVX_NUM_CHARS_UPPER_LETTER_N: aType = "upperLetter"_ostr; break; |
| |
| case SVX_NUM_CHARS_LOWER_LETTER: |
| case SVX_NUM_CHARS_LOWER_LETTER_N: aType = "lowerLetter"_ostr; break; |
| |
| case SVX_NUM_ROMAN_UPPER: aType = "upperRoman"_ostr; break; |
| case SVX_NUM_ROMAN_LOWER: aType = "lowerRoman"_ostr; break; |
| case SVX_NUM_ARABIC: aType = "decimal"_ostr; break; |
| |
| case SVX_NUM_BITMAP: |
| case SVX_NUM_CHAR_SPECIAL: aType = "bullet"_ostr; break; |
| |
| case style::NumberingType::CHARS_HEBREW: aType = "hebrew2"_ostr; break; |
| case style::NumberingType::NUMBER_HEBREW: aType = "hebrew1"_ostr; break; |
| case style::NumberingType::NUMBER_NONE: aType = "none"_ostr; break; |
| case style::NumberingType::FULLWIDTH_ARABIC: aType="decimalFullWidth"_ostr; break; |
| case style::NumberingType::TIAN_GAN_ZH: aType="ideographTraditional"_ostr; break; |
| case style::NumberingType::DI_ZI_ZH: aType="ideographZodiac"_ostr; break; |
| case style::NumberingType::NUMBER_LOWER_ZH: |
| aType="taiwaneseCountingThousand"_ostr; |
| if (pOutSet) { |
| const SvxLanguageItem& rLang = pOutSet->Get( RES_CHRATR_CJK_LANGUAGE); |
| const LanguageType eLang = rLang.GetLanguage(); |
| |
| if (LANGUAGE_CHINESE_SIMPLIFIED == eLang) { |
| aType="chineseCountingThousand"_ostr; |
| } |
| } |
| break; |
| case style::NumberingType::NUMBER_UPPER_ZH_TW: aType="ideographLegalTraditional"_ostr;break; |
| case style::NumberingType::NUMBER_UPPER_ZH: aType="chineseLegalSimplified"_ostr; break; |
| case style::NumberingType::NUMBER_TRADITIONAL_JA: aType="japaneseLegal"_ostr;break; |
| case style::NumberingType::AIU_FULLWIDTH_JA: aType="aiueoFullWidth"_ostr;break; |
| case style::NumberingType::AIU_HALFWIDTH_JA: aType="aiueo"_ostr;break; |
| case style::NumberingType::IROHA_FULLWIDTH_JA: aType="iroha"_ostr;break; |
| case style::NumberingType::IROHA_HALFWIDTH_JA: aType="irohaFullWidth"_ostr;break; |
| case style::NumberingType::HANGUL_SYLLABLE_KO: aType="ganada"_ostr;break; |
| case style::NumberingType::HANGUL_JAMO_KO: aType="chosung"_ostr;break; |
| case style::NumberingType::NUMBER_HANGUL_KO: aType="koreanCounting"_ostr; break; |
| case style::NumberingType::NUMBER_LEGAL_KO: aType = "koreanLegal"_ostr; break; |
| case style::NumberingType::NUMBER_DIGITAL_KO: aType = "koreanDigital"_ostr; break; |
| case style::NumberingType::NUMBER_DIGITAL2_KO: aType = "koreanDigital2"_ostr; break; |
| case style::NumberingType::CIRCLE_NUMBER: aType="decimalEnclosedCircle"_ostr; break; |
| case style::NumberingType::CHARS_ARABIC: aType="arabicAlpha"_ostr; break; |
| case style::NumberingType::CHARS_ARABIC_ABJAD: aType="arabicAbjad"_ostr; break; |
| case style::NumberingType::CHARS_THAI: aType="thaiLetters"_ostr; break; |
| case style::NumberingType::CHARS_PERSIAN: |
| case style::NumberingType::CHARS_NEPALI: aType="hindiVowels"_ostr; break; |
| case style::NumberingType::CHARS_CYRILLIC_UPPER_LETTER_RU: |
| case style::NumberingType::CHARS_CYRILLIC_UPPER_LETTER_N_RU: aType = "russianUpper"_ostr; break; |
| case style::NumberingType::CHARS_CYRILLIC_LOWER_LETTER_RU: |
| case style::NumberingType::CHARS_CYRILLIC_LOWER_LETTER_N_RU: aType = "russianLower"_ostr; break; |
| case style::NumberingType::TEXT_NUMBER: aType="ordinal"_ostr; break; |
| case style::NumberingType::TEXT_CARDINAL: aType="cardinalText"_ostr; break; |
| case style::NumberingType::TEXT_ORDINAL: aType="ordinalText"_ostr; break; |
| case style::NumberingType::SYMBOL_CHICAGO: aType="chicago"_ostr; break; |
| case style::NumberingType::ARABIC_ZERO: aType = "decimalZero"_ostr; break; |
| case style::NumberingType::ARABIC_ZERO3: |
| aType = "custom"_ostr; |
| rFormat = "001, 002, 003, ..."_ostr; |
| break; |
| case style::NumberingType::ARABIC_ZERO4: |
| aType = "custom"_ostr; |
| rFormat = "0001, 0002, 0003, ..."_ostr; |
| break; |
| case style::NumberingType::ARABIC_ZERO5: |
| aType = "custom"_ostr; |
| rFormat = "00001, 00002, 00003, ..."_ostr; |
| break; |
| /* |
| Fallback the rest to the suggested default. |
| case style::NumberingType::NATIVE_NUMBERING: |
| case style::NumberingType::HANGUL_CIRCLED_JAMO_KO: |
| case style::NumberingType::HANGUL_CIRCLED_SYLLABLE_KO: |
| case style::NumberingType::CHARS_GREEK_UPPER_LETTER: |
| case style::NumberingType::CHARS_GREEK_LOWER_LETTER: |
| case style::NumberingType::PAGE_DESCRIPTOR: |
| case style::NumberingType::TRANSLITERATION: |
| case style::NumberingType::CHARS_KHMER: |
| case style::NumberingType::CHARS_LAO: |
| case style::NumberingType::CHARS_TIBETAN: |
| case style::NumberingType::CHARS_CYRILLIC_UPPER_LETTER_BG: |
| case style::NumberingType::CHARS_CYRILLIC_LOWER_LETTER_BG: |
| case style::NumberingType::CHARS_CYRILLIC_UPPER_LETTER_N_BG: |
| case style::NumberingType::CHARS_CYRILLIC_LOWER_LETTER_N_BG: |
| case style::NumberingType::CHARS_MYANMAR: |
| case style::NumberingType::CHARS_CYRILLIC_UPPER_LETTER_SR: |
| case style::NumberingType::CHARS_CYRILLIC_LOWER_LETTER_SR: |
| case style::NumberingType::CHARS_CYRILLIC_UPPER_LETTER_N_SR: |
| case style::NumberingType::CHARS_CYRILLIC_LOWER_LETTER_N_SR: |
| case style::NumberingType::CHARS_CYRILLIC_UPPER_LETTER_UK: |
| case style::NumberingType::CHARS_CYRILLIC_LOWER_LETTER_UK: |
| case style::NumberingType::CHARS_CYRILLIC_UPPER_LETTER_N_UK: |
| case style::NumberingType::CHARS_CYRILLIC_LOWER_LETTER_N_UK: |
| */ |
| default: break; |
| } |
| return aType; |
| } |
| |
| |
| void DocxAttributeOutput::SectionPageNumbering( sal_uInt16 nNumType, const ::std::optional<sal_uInt16>& oPageRestartNumber ) |
| { |
| // FIXME Not called properly with page styles like "First Page" |
| |
| rtl::Reference<FastAttributeList> pAttr = FastSerializerHelper::createAttrList(); |
| |
| // std::nullopt means no restart: then don't output that attribute if it is negative |
| if ( oPageRestartNumber ) |
| pAttr->add( FSNS( XML_w, XML_start ), OString::number( *oPageRestartNumber ) ); |
| |
| // nNumType corresponds to w:fmt. See WW8Export::GetNumId() for more precisions |
| OString aCustomFormat; |
| OString aFormat(lcl_ConvertNumberingType(nNumType, nullptr, aCustomFormat)); |
| if (!aFormat.isEmpty() && aCustomFormat.isEmpty()) |
| pAttr->add(FSNS(XML_w, XML_fmt), aFormat); |
| |
| m_pSerializer->singleElementNS( XML_w, XML_pgNumType, pAttr ); |
| |
| // see 2.6.12 pgNumType (Page Numbering Settings) |
| SAL_INFO("sw.ww8", "TODO DocxAttributeOutput::SectionPageNumbering()" ); |
| } |
| |
| void DocxAttributeOutput::SectionType( sal_uInt8 nBreakCode ) |
| { |
| /* break code: 0 No break, 1 New column |
| 2 New page, 3 Even page, 4 Odd page |
| */ |
| const char* pType; |
| switch ( nBreakCode ) |
| { |
| case 1: pType = "nextColumn"; break; |
| case 2: pType = "nextPage"; break; |
| case 3: pType = "evenPage"; break; |
| case 4: pType = "oddPage"; break; |
| default: pType = "continuous"; break; |
| } |
| |
| m_pSerializer->singleElementNS(XML_w, XML_type, FSNS(XML_w, XML_val), pType); |
| } |
| |
| void DocxAttributeOutput::TextVerticalAdjustment( const drawing::TextVerticalAdjust nVA ) |
| { |
| switch( nVA ) |
| { |
| case drawing::TextVerticalAdjust_CENTER: |
| m_pSerializer->singleElementNS(XML_w, XML_vAlign, FSNS(XML_w, XML_val), "center"); |
| break; |
| case drawing::TextVerticalAdjust_BOTTOM: |
| m_pSerializer->singleElementNS(XML_w, XML_vAlign, FSNS(XML_w, XML_val), "bottom"); |
| break; |
| case drawing::TextVerticalAdjust_BLOCK: //justify |
| m_pSerializer->singleElementNS(XML_w, XML_vAlign, FSNS(XML_w, XML_val), "both"); |
| break; |
| default: |
| break; |
| } |
| } |
| |
| void DocxAttributeOutput::StartFont( const OUString& rFamilyName ) const |
| { |
| m_pSerializer->startElementNS(XML_w, XML_font, FSNS(XML_w, XML_name), rFamilyName); |
| } |
| |
| void DocxAttributeOutput::EndFont() const |
| { |
| m_pSerializer->endElementNS( XML_w, XML_font ); |
| } |
| |
| void DocxAttributeOutput::FontAlternateName( const OUString& rName ) const |
| { |
| m_pSerializer->singleElementNS(XML_w, XML_altName, FSNS(XML_w, XML_val), rName); |
| } |
| |
| void DocxAttributeOutput::FontCharset( sal_uInt8 nCharSet, rtl_TextEncoding nEncoding ) const |
| { |
| rtl::Reference<FastAttributeList> pAttr = FastSerializerHelper::createAttrList(); |
| |
| OString aCharSet( OString::number( nCharSet, 16 ) ); |
| if ( aCharSet.getLength() == 1 ) |
| aCharSet = "0" + aCharSet; |
| pAttr->add(FSNS(XML_w, XML_val), aCharSet); |
| |
| if (GetExport().GetFilter().getVersion() != oox::core::ECMA_376_1ST_EDITION) |
| { |
| if( const char* charset = rtl_getMimeCharsetFromTextEncoding( nEncoding )) |
| pAttr->add( FSNS( XML_w, XML_characterSet ), charset ); |
| } |
| |
| m_pSerializer->singleElementNS( XML_w, XML_charset, pAttr ); |
| } |
| |
| void DocxAttributeOutput::FontFamilyType( FontFamily eFamily ) const |
| { |
| const char* pFamily; |
| switch ( eFamily ) |
| { |
| case FAMILY_ROMAN: pFamily = "roman"; break; |
| case FAMILY_SWISS: pFamily = "swiss"; break; |
| case FAMILY_MODERN: pFamily = "modern"; break; |
| case FAMILY_SCRIPT: pFamily = "script"; break; |
| case FAMILY_DECORATIVE: pFamily = "decorative"; break; |
| default: pFamily = "auto"; break; // no font family |
| } |
| |
| m_pSerializer->singleElementNS(XML_w, XML_family, FSNS(XML_w, XML_val), pFamily); |
| } |
| |
| void DocxAttributeOutput::FontPitchType( FontPitch ePitch ) const |
| { |
| const char* pPitch; |
| switch ( ePitch ) |
| { |
| case PITCH_VARIABLE: pPitch = "variable"; break; |
| case PITCH_FIXED: pPitch = "fixed"; break; |
| default: pPitch = "default"; break; // no info about the pitch |
| } |
| |
| m_pSerializer->singleElementNS(XML_w, XML_pitch, FSNS(XML_w, XML_val), pPitch); |
| } |
| |
| void DocxAttributeOutput::EmbedFont( std::u16string_view name, FontFamily family, FontPitch pitch ) |
| { |
| if( !m_rExport.m_rDoc.getIDocumentSettingAccess().get( DocumentSettingId::EMBED_FONTS )) |
| return; // no font embedding with this document |
| bool foundFont |
| = EmbedFontStyle(name, XML_embedRegular, family, ITALIC_NONE, WEIGHT_NORMAL, pitch); |
| foundFont |
| = EmbedFontStyle(name, XML_embedBold, family, ITALIC_NONE, WEIGHT_BOLD, pitch) || foundFont; |
| foundFont = EmbedFontStyle(name, XML_embedItalic, family, ITALIC_NORMAL, WEIGHT_NORMAL, pitch) |
| || foundFont; |
| foundFont = EmbedFontStyle(name, XML_embedBoldItalic, family, ITALIC_NORMAL, WEIGHT_BOLD, pitch) |
| || foundFont; |
| if (!foundFont) |
| EmbedFontStyle(name, XML_embedRegular, family, ITALIC_NONE, WEIGHT_DONTKNOW, pitch); |
| } |
| |
| static char toHexChar( int value ) |
| { |
| return value >= 10 ? value + 'A' - 10 : value + '0'; |
| } |
| |
| bool DocxAttributeOutput::EmbedFontStyle(std::u16string_view name, int tag, FontFamily family, |
| FontItalic italic, FontWeight weight, FontPitch pitch) |
| { |
| // Embed font if at least viewing is allowed (in which case the opening app must check |
| // the font license rights too and open either read-only or not use the font for editing). |
| OUString fontUrl = EmbeddedFontsHelper::fontFileUrl( name, family, italic, weight, pitch, |
| EmbeddedFontsHelper::FontRights::ViewingAllowed ); |
| if( fontUrl.isEmpty()) |
| return false; |
| // TODO IDocumentSettingAccess::EMBED_SYSTEM_FONTS |
| if( !m_FontFilesMap.count( fontUrl )) |
| { |
| osl::File file( fontUrl ); |
| if( file.open( osl_File_OpenFlag_Read ) != osl::File::E_None ) |
| return false; |
| uno::Reference< css::io::XOutputStream > xOutStream = m_rExport.GetFilter().openFragmentStream( |
| "word/fonts/font" + OUString::number(m_nextFontId) + ".odttf", |
| u"application/vnd.openxmlformats-officedocument.obfuscatedFont"_ustr ); |
| // Not much point in trying hard with the obfuscation key, whoever reads the spec can read the font anyway, |
| // so just alter the first and last part of the key. |
| char fontKeyStr[] = "{00014A78-CABC-4EF0-12AC-5CD89AEFDE00}"; |
| sal_uInt8 fontKey[ 16 ] = { 0, 0xDE, 0xEF, 0x9A, 0xD8, 0x5C, 0xAC, 0x12, 0xF0, 0x4E, |
| 0xBC, 0xCA, 0x78, 0x4A, 0x01, 0 }; |
| fontKey[ 0 ] = fontKey[ 15 ] = m_nextFontId % 256; |
| fontKeyStr[ 1 ] = fontKeyStr[ 35 ] = toHexChar(( m_nextFontId % 256 ) / 16 ); |
| fontKeyStr[ 2 ] = fontKeyStr[ 36 ] = toHexChar(( m_nextFontId % 256 ) % 16 ); |
| unsigned char buffer[ 4096 ]; |
| sal_uInt64 readSize; |
| file.read( buffer, 32, readSize ); |
| if( readSize < 32 ) |
| { |
| SAL_WARN( "sw.ww8", "Font file size too small (" << fontUrl << ")" ); |
| xOutStream->closeOutput(); |
| return false; |
| } |
| for( int i = 0; |
| i < 16; |
| ++i ) |
| { |
| buffer[ i ] ^= fontKey[ i ]; |
| buffer[ i + 16 ] ^= fontKey[ i ]; |
| } |
| xOutStream->writeBytes( uno::Sequence< sal_Int8 >( reinterpret_cast< const sal_Int8* >( buffer ), 32 )); |
| for(;;) |
| { |
| sal_Bool eof; |
| if( file.isEndOfFile( &eof ) != osl::File::E_None ) |
| { |
| SAL_WARN( "sw.ww8", "Error reading font file " << fontUrl ); |
| xOutStream->closeOutput(); |
| return false; |
| } |
| if( eof ) |
| break; |
| if( file.read( buffer, 4096, readSize ) != osl::File::E_None ) |
| { |
| SAL_WARN( "sw.ww8", "Error reading font file " << fontUrl ); |
| xOutStream->closeOutput(); |
| return false; |
| } |
| if( readSize == 0 ) |
| break; |
| // coverity[overrun-buffer-arg : FALSE] - coverity has difficulty with css::uno::Sequence |
| xOutStream->writeBytes( uno::Sequence< sal_Int8 >( reinterpret_cast< const sal_Int8* >( buffer ), readSize )); |
| } |
| xOutStream->closeOutput(); |
| EmbeddedFontRef ref; |
| ref.relId = OUStringToOString( GetExport().GetFilter().addRelation( m_pSerializer->getOutputStream(), |
| oox::getRelationship(Relationship::FONT), |
| Concat2View("fonts/font" + OUString::number( m_nextFontId ) + ".odttf") ), RTL_TEXTENCODING_UTF8 ); |
| ref.fontKey = fontKeyStr; |
| m_FontFilesMap[ fontUrl ] = ref; |
| ++m_nextFontId; |
| } |
| m_pSerializer->singleElementNS( XML_w, tag, |
| FSNS( XML_r, XML_id ), m_FontFilesMap[ fontUrl ].relId, |
| FSNS( XML_w, XML_fontKey ), m_FontFilesMap[ fontUrl ].fontKey ); |
| return true; |
| } |
| |
| OString DocxAttributeOutput::TransHighlightColor( sal_uInt8 nIco ) |
| { |
| switch (nIco) |
| { |
| case 0: return "none"_ostr; break; |
| case 1: return "black"_ostr; break; |
| case 2: return "blue"_ostr; break; |
| case 3: return "cyan"_ostr; break; |
| case 4: return "green"_ostr; break; |
| case 5: return "magenta"_ostr; break; |
| case 6: return "red"_ostr; break; |
| case 7: return "yellow"_ostr; break; |
| case 8: return "white"_ostr; break; |
| case 9: return "darkBlue"_ostr; break; |
| case 10: return "darkCyan"_ostr; break; |
| case 11: return "darkGreen"_ostr; break; |
| case 12: return "darkMagenta"_ostr; break; |
| case 13: return "darkRed"_ostr; break; |
| case 14: return "darkYellow"_ostr; break; |
| case 15: return "darkGray"_ostr; break; |
| case 16: return "lightGray"_ostr; break; |
| default: return OString(); break; |
| } |
| } |
| |
| void DocxAttributeOutput::NumberingDefinition( sal_uInt16 nId, const SwNumRule &rRule ) |
| { |
| // nId is the same both for abstract numbering definition as well as the |
| // numbering definition itself |
| // TODO check that this is actually true & fix if not ;-) |
| OString aId( OString::number( nId ) ); |
| |
| m_pSerializer->startElementNS(XML_w, XML_num, FSNS(XML_w, XML_numId), aId); |
| |
| m_pSerializer->singleElementNS(XML_w, XML_abstractNumId, FSNS(XML_w, XML_val), aId); |
| |
| #if OSL_DEBUG_LEVEL > 1 |
| // TODO ww8 version writes this, anything to do about it here? |
| if ( rRule.IsContinusNum() ) |
| SAL_INFO("sw", "TODO DocxAttributeOutput::NumberingDefinition()" ); |
| #else |
| (void) rRule; // to quiet the warning... |
| #endif |
| |
| m_pSerializer->endElementNS( XML_w, XML_num ); |
| } |
| |
| // Not all attributes of SwNumFormat are important for export, so can't just use embedded in |
| // that classes comparison. |
| static bool lcl_ListLevelsAreDifferentForExport(const SwNumFormat & rFormat1, const SwNumFormat & rFormat2) |
| { |
| if (rFormat1 == rFormat2) |
| // They are equal, nothing to do |
| return false; |
| |
| if (!rFormat1.GetCharFormat() != !rFormat2.GetCharFormat()) |
| // One has charformat, other not. they are different |
| return true; |
| |
| if (rFormat1.GetCharFormat() && rFormat2.GetCharFormat()) |
| { |
| const SwAttrSet & a1 = rFormat1.GetCharFormat()->GetAttrSet(); |
| const SwAttrSet & a2 = rFormat2.GetCharFormat()->GetAttrSet(); |
| |
| if (!(a1 == a2)) |
| // Difference in charformat: they are different |
| return true; |
| } |
| |
| // Compare numformats with empty charformats |
| SwNumFormat modified1 = rFormat1; |
| SwNumFormat modified2 = rFormat2; |
| modified1.SetCharFormatName(OUString()); |
| modified2.SetCharFormatName(OUString()); |
| modified1.SetCharFormat(nullptr); |
| modified2.SetCharFormat(nullptr); |
| return modified1 != modified2; |
| } |
| |
| void DocxAttributeOutput::OverrideNumberingDefinition( |
| SwNumRule const& rRule, |
| sal_uInt16 const nNum, sal_uInt16 const nAbstractNum, const std::map< size_t, size_t > & rLevelOverrides ) |
| { |
| m_pSerializer->startElementNS(XML_w, XML_num, FSNS(XML_w, XML_numId), OString::number(nNum)); |
| |
| m_pSerializer->singleElementNS(XML_w, XML_abstractNumId, FSNS(XML_w, XML_val), OString::number(nAbstractNum)); |
| |
| SwNumRule const& rAbstractRule = *(*m_rExport.m_pUsedNumTable)[nAbstractNum - 1]; |
| sal_uInt8 const nLevels = static_cast<sal_uInt8>(rRule.IsContinusNum() |
| ? WW8ListManager::nMinLevel : WW8ListManager::nMaxLevel); |
| sal_uInt8 nPreviousOverrideLevel = 0; |
| for (sal_uInt8 nLevel = 0; nLevel < nLevels; ++nLevel) |
| { |
| const auto levelOverride = rLevelOverrides.find(nLevel); |
| bool bListsAreDifferent = lcl_ListLevelsAreDifferentForExport(rRule.Get(nLevel), rAbstractRule.Get(nLevel)); |
| |
| // Export list override only if it is different to abstract one |
| // or we have a level numbering override |
| if (bListsAreDifferent || levelOverride != rLevelOverrides.end()) |
| { |
| // If there are "gaps" in w:lvlOverride numbers, MS Word can have issues with numbering. |
| // So we need to emit default override tokens up to current one. |
| while (nPreviousOverrideLevel < nLevel) |
| { |
| const SwNumFormat& rFormat = rRule.Get(nPreviousOverrideLevel); |
| m_pSerializer->startElementNS(XML_w, XML_lvlOverride, FSNS(XML_w, XML_ilvl), OString::number(nPreviousOverrideLevel)); |
| // tdf#153104: absent startOverride is treated by Word as "startOverride value 0". |
| m_pSerializer->singleElementNS(XML_w, XML_startOverride, FSNS(XML_w, XML_val), OString::number(rFormat.GetStart())); |
| m_pSerializer->endElementNS(XML_w, XML_lvlOverride); |
| nPreviousOverrideLevel++; |
| } |
| |
| m_pSerializer->startElementNS(XML_w, XML_lvlOverride, FSNS(XML_w, XML_ilvl), OString::number(nLevel)); |
| |
| if (bListsAreDifferent) |
| { |
| GetExport().NumberingLevel(rRule, nLevel); |
| } |
| if (levelOverride != rLevelOverrides.end()) |
| { |
| // list numbering restart override |
| m_pSerializer->singleElementNS(XML_w, XML_startOverride, |
| FSNS(XML_w, XML_val), OString::number(levelOverride->second)); |
| } |
| |
| m_pSerializer->endElementNS(XML_w, XML_lvlOverride); |
| } |
| } |
| |
| m_pSerializer->endElementNS( XML_w, XML_num ); |
| } |
| |
| void DocxAttributeOutput::StartAbstractNumbering( sal_uInt16 nId ) |
| { |
| const SwNumRule* pRule = (*m_rExport.m_pUsedNumTable)[nId - 1]; |
| m_bExportingOutline = pRule && pRule->IsOutlineRule(); |
| m_pSerializer->startElementNS( XML_w, XML_abstractNum, |
| FSNS( XML_w, XML_abstractNumId ), OString::number(nId) ); |
| } |
| |
| void DocxAttributeOutput::EndAbstractNumbering() |
| { |
| m_pSerializer->endElementNS( XML_w, XML_abstractNum ); |
| } |
| |
| void DocxAttributeOutput::NumberingLevel( sal_uInt8 nLevel, |
| sal_uInt16 nStart, |
| sal_uInt16 nNumberingType, |
| SvxAdjust eAdjust, |
| const sal_uInt8 * /*pNumLvlPos*/, |
| sal_uInt8 nFollow, |
| const wwFont *pFont, |
| const SfxItemSet *pOutSet, |
| sal_Int16 nIndentAt, |
| sal_Int16 nFirstLineIndex, |
| sal_Int16 nListTabPos, |
| const OUString &rNumberingString, |
| const SvxBrushItem* pBrush, |
| bool isLegal) |
| { |
| m_pSerializer->startElementNS(XML_w, XML_lvl, FSNS(XML_w, XML_ilvl), OString::number(nLevel)); |
| |
| // start with the nStart value. Do not write w:start if Numbered Lists |
| // starts from zero.As it's an optional parameter. |
| // refer ECMA 376 Second edition Part-1 |
| if(0 != nLevel || 0 != nStart) |
| { |
| m_pSerializer->singleElementNS( XML_w, XML_start, |
| FSNS( XML_w, XML_val ), OString::number(nStart) ); |
| } |
| |
| if (m_bExportingOutline) |
| { |
| sal_uInt16 nId = m_rExport.m_pStyles->GetHeadingParagraphStyleId( nLevel ); |
| if ( nId != SAL_MAX_UINT16 ) |
| m_pSerializer->singleElementNS( XML_w, XML_pStyle , |
| FSNS( XML_w, XML_val ), m_rExport.m_pStyles->GetStyleId(nId) ); |
| } |
| |
| if (isLegal) |
| m_pSerializer->singleElementNS(XML_w, XML_isLgl); |
| |
| // format |
| OString aCustomFormat; |
| OString aFormat(lcl_ConvertNumberingType(nNumberingType, pOutSet, aCustomFormat, "decimal"_ostr)); |
| |
| { |
| if (aCustomFormat.isEmpty()) |
| { |
| m_pSerializer->singleElementNS(XML_w, XML_numFmt, FSNS(XML_w, XML_val), aFormat); |
| } |
| else |
| { |
| m_pSerializer->startElementNS(XML_mc, XML_AlternateContent); |
| m_pSerializer->startElementNS(XML_mc, XML_Choice, XML_Requires, "w14"); |
| |
| m_pSerializer->singleElementNS(XML_w, XML_numFmt, FSNS(XML_w, XML_val), aFormat, |
| FSNS(XML_w, XML_format), aCustomFormat); |
| |
| m_pSerializer->endElementNS(XML_mc, XML_Choice); |
| m_pSerializer->startElementNS(XML_mc, XML_Fallback); |
| m_pSerializer->singleElementNS(XML_w, XML_numFmt, FSNS(XML_w, XML_val), "decimal"); |
| m_pSerializer->endElementNS(XML_mc, XML_Fallback); |
| m_pSerializer->endElementNS(XML_mc, XML_AlternateContent); |
| } |
| } |
| |
| // suffix |
| const char *pSuffix = nullptr; |
| switch ( nFollow ) |
| { |
| case 1: pSuffix = "space"; break; |
| case 2: pSuffix = "nothing"; break; |
| default: /*pSuffix = "tab";*/ break; |
| } |
| if ( pSuffix ) |
| m_pSerializer->singleElementNS(XML_w, XML_suff, FSNS(XML_w, XML_val), pSuffix); |
| |
| // text |
| OUStringBuffer aBuffer( rNumberingString.getLength() + WW8ListManager::nMaxLevel ); |
| |
| const sal_Unicode *pPrev = rNumberingString.getStr(); |
| const sal_Unicode *pIt = rNumberingString.getStr(); |
| while ( pIt < rNumberingString.getStr() + rNumberingString.getLength() ) |
| { |
| // convert the level values to %NUMBER form |
| // (we don't use pNumLvlPos at all) |
| // FIXME so far we support the ww8 limit of levels only |
| if ( *pIt < sal_Unicode( WW8ListManager::nMaxLevel ) ) |
| { |
| aBuffer.append( OUString::Concat(std::u16string_view(pPrev, pIt - pPrev)) |
| + "%" |
| + OUString::number(sal_Int32( *pIt ) + 1 )); |
| |
| pPrev = pIt + 1; |
| } |
| ++pIt; |
| } |
| if ( pPrev < pIt ) |
| aBuffer.append( pPrev, pIt - pPrev ); |
| |
| // If bullet char is empty, set lvlText as empty |
| if ( rNumberingString == OUStringChar('\0') && nNumberingType == SVX_NUM_CHAR_SPECIAL ) |
| { |
| m_pSerializer->singleElementNS(XML_w, XML_lvlText, FSNS(XML_w, XML_val), ""); |
| } |
| else |
| { |
| // Writer's "zero width space" suffix is necessary, so that LabelFollowedBy shows up, but Word doesn't require that. |
| OUString aLevelText = aBuffer.makeStringAndClear(); |
| static OUString aZeroWidthSpace(u'\x200B'); |
| if (aLevelText == aZeroWidthSpace) |
| aLevelText.clear(); |
| m_pSerializer->singleElementNS(XML_w, XML_lvlText, FSNS(XML_w, XML_val), aLevelText); |
| } |
| |
| // bullet |
| if (nNumberingType == SVX_NUM_BITMAP && pBrush) |
| { |
| int nIndex = m_rExport.GetGrfIndex(*pBrush); |
| if (nIndex != -1) |
| { |
| m_pSerializer->singleElementNS(XML_w, XML_lvlPicBulletId, |
| FSNS(XML_w, XML_val), OString::number(nIndex)); |
| } |
| } |
| |
| // justification |
| const char *pJc; |
| bool const ecmaDialect = m_rExport.GetFilter().getVersion() == oox::core::ECMA_376_1ST_EDITION; |
| switch ( eAdjust ) |
| { |
| case SvxAdjust::Center: pJc = "center"; break; |
| case SvxAdjust::Right: pJc = !ecmaDialect ? "end" : "right"; break; |
| default: pJc = !ecmaDialect ? "start" : "left"; break; |
| } |
| m_pSerializer->singleElementNS(XML_w, XML_lvlJc, FSNS(XML_w, XML_val), pJc); |
| |
| // indentation |
| m_pSerializer->startElementNS(XML_w, XML_pPr); |
| if( nListTabPos >= 0 ) |
| { |
| m_pSerializer->startElementNS(XML_w, XML_tabs); |
| m_pSerializer->singleElementNS( XML_w, XML_tab, |
| FSNS( XML_w, XML_val ), "num", |
| FSNS( XML_w, XML_pos ), OString::number(nListTabPos) ); |
| m_pSerializer->endElementNS( XML_w, XML_tabs ); |
| } |
| |
| sal_Int32 nToken = ecmaDialect ? XML_left : XML_start; |
| sal_Int32 nIndentToken = nFirstLineIndex > 0 ? XML_firstLine : XML_hanging; |
| m_pSerializer->singleElementNS( XML_w, XML_ind, |
| FSNS( XML_w, nToken ), OString::number(nIndentAt), |
| FSNS( XML_w, nIndentToken ), OString::number(abs(nFirstLineIndex)) ); |
| m_pSerializer->endElementNS( XML_w, XML_pPr ); |
| |
| // font |
| if ( pOutSet ) |
| { |
| m_pSerializer->startElementNS(XML_w, XML_rPr); |
| |
| SfxItemSet aTempSet(*pOutSet); |
| if ( pFont ) |
| { |
| GetExport().GetId( *pFont ); // ensure font info is written to fontTable.xml |
| OString aFamilyName( OUStringToOString( pFont->GetFamilyName(), RTL_TEXTENCODING_UTF8 ) ); |
| m_pSerializer->singleElementNS( XML_w, XML_rFonts, |
| FSNS( XML_w, XML_ascii ), aFamilyName, |
| FSNS( XML_w, XML_hAnsi ), aFamilyName, |
| FSNS( XML_w, XML_cs ), aFamilyName, |
| FSNS( XML_w, XML_hint ), "default" ); |
| aTempSet.ClearItem(RES_CHRATR_FONT); |
| aTempSet.ClearItem(RES_CHRATR_CTL_FONT); |
| } |
| m_rExport.OutputItemSet(aTempSet, false, true, i18n::ScriptType::LATIN, m_rExport.m_bExportModeRTF); |
| |
| WriteCollectedRunProperties(); |
| |
| m_pSerializer->endElementNS( XML_w, XML_rPr ); |
| } |
| |
| // TODO anything to do about nListTabPos? |
| |
| m_pSerializer->endElementNS( XML_w, XML_lvl ); |
| } |
| |
| void DocxAttributeOutput::CharCaseMap( const SvxCaseMapItem& rCaseMap ) |
| { |
| switch ( rCaseMap.GetValue() ) |
| { |
| case SvxCaseMap::SmallCaps: |
| m_pSerializer->singleElementNS(XML_w, XML_smallCaps); |
| break; |
| case SvxCaseMap::Uppercase: |
| m_pSerializer->singleElementNS(XML_w, XML_caps); |
| break; |
| default: // Something that ooxml does not support |
| m_pSerializer->singleElementNS(XML_w, XML_smallCaps, FSNS(XML_w, XML_val), "false"); |
| m_pSerializer->singleElementNS(XML_w, XML_caps, FSNS(XML_w, XML_val), "false"); |
| break; |
| } |
| } |
| |
| void DocxAttributeOutput::CharColor(const SvxColorItem& rColorItem) |
| { |
| const Color aColor = rColorItem.getColor(); |
| const model::ComplexColor aComplexColor = rColorItem.getComplexColor(); |
| |
| OString aColorString = msfilter::util::ConvertColor(aColor); |
| |
| std::string_view pExistingValue; |
| if (m_pColorAttrList.is() && m_pColorAttrList->getAsView(FSNS(XML_w, XML_val), pExistingValue)) |
| { |
| assert(aColorString.equalsL(pExistingValue.data(), pExistingValue.size())); |
| return; |
| } |
| |
| lclAddThemeColorAttributes(m_pColorAttrList, aComplexColor); |
| |
| AddToAttrList(m_pColorAttrList, FSNS(XML_w, XML_val), aColorString); |
| m_nCharTransparence = 255 - aColor.GetAlpha(); |
| } |
| |
| void DocxAttributeOutput::CharContour( const SvxContourItem& rContour ) |
| { |
| if ( rContour.GetValue() ) |
| m_pSerializer->singleElementNS(XML_w, XML_outline); |
| else |
| m_pSerializer->singleElementNS(XML_w, XML_outline, FSNS(XML_w, XML_val), "false"); |
| } |
| |
| void DocxAttributeOutput::CharCrossedOut( const SvxCrossedOutItem& rCrossedOut ) |
| { |
| switch ( rCrossedOut.GetStrikeout() ) |
| { |
| case STRIKEOUT_DOUBLE: |
| m_pSerializer->singleElementNS(XML_w, XML_dstrike); |
| break; |
| case STRIKEOUT_NONE: |
| m_pSerializer->singleElementNS(XML_w, XML_dstrike, FSNS(XML_w, XML_val), "false"); |
| m_pSerializer->singleElementNS(XML_w, XML_strike, FSNS(XML_w, XML_val), "false"); |
| break; |
| default: |
| m_pSerializer->singleElementNS(XML_w, XML_strike); |
| break; |
| } |
| } |
| |
| void DocxAttributeOutput::CharEscapement( const SvxEscapementItem& rEscapement ) |
| { |
| OString sIss; |
| short nEsc = rEscapement.GetEsc(), nProp = rEscapement.GetProportionalHeight(); |
| |
| bool bParaStyle = false; |
| if (m_rExport.m_bStyDef && m_rExport.m_pCurrentStyle) |
| { |
| bParaStyle = m_rExport.m_pCurrentStyle->Which() == RES_TXTFMTCOLL; |
| } |
| |
| // Simplify styles to avoid impossible complexity. Import and export as defaults only |
| if ( m_rExport.m_bStyDef && nEsc && !(bParaStyle && nEsc < 0)) |
| { |
| nProp = DFLT_ESC_PROP; |
| nEsc = (nEsc > 0) ? DFLT_ESC_AUTO_SUPER : DFLT_ESC_AUTO_SUB; |
| } |
| |
| if ( !nEsc ) |
| { |
| sIss = "baseline"_ostr; |
| nEsc = 0; |
| nProp = 100; |
| } |
| else if ( DFLT_ESC_PROP == nProp || nProp < 1 || nProp > 100 ) |
| { |
| if ( DFLT_ESC_SUB == nEsc || DFLT_ESC_AUTO_SUB == nEsc ) |
| sIss = "subscript"_ostr; |
| else if ( DFLT_ESC_SUPER == nEsc || DFLT_ESC_AUTO_SUPER == nEsc ) |
| sIss = "superscript"_ostr; |
| } |
| else if ( DFLT_ESC_AUTO_SUPER == nEsc ) |
| { |
| // Raised by the differences between the ascenders (ascent = baseline to top of highest letter). |
| // The ascent is generally about 80% of the total font height. |
| // That is why DFLT_ESC_PROP (58) leads to 33% (DFLT_ESC_SUPER) |
| nEsc = .8 * (100 - nProp); |
| } |
| else if ( DFLT_ESC_AUTO_SUB == nEsc ) |
| { |
| // Lowered by the differences between the descenders (descent = baseline to bottom of lowest letter). |
| // The descent is generally about 20% of the total font height. |
| // That is why DFLT_ESC_PROP (58) leads to 8% (DFLT_ESC_SUB) |
| nEsc = .2 * -(100 - nProp); |
| } |
| |
| if ( !sIss.isEmpty() ) |
| m_pSerializer->singleElementNS(XML_w, XML_vertAlign, FSNS(XML_w, XML_val), sIss); |
| |
| if (!(sIss.isEmpty() || sIss.match("baseline"))) |
| return; |
| |
| const SvxFontHeightItem& rItem = m_rExport.GetItem(RES_CHRATR_FONTSIZE); |
| float fHeight = rItem.GetHeight(); |
| OString sPos = OString::number( round(( fHeight * nEsc ) / 1000) ); |
| m_pSerializer->singleElementNS(XML_w, XML_position, FSNS(XML_w, XML_val), sPos); |
| |
| if( ( 100 != nProp || sIss.match( "baseline" ) ) && !m_rExport.m_bFontSizeWritten ) |
| { |
| OString sSize = OString::number( round(( fHeight * nProp ) / 1000) ); |
| m_pSerializer->singleElementNS(XML_w, XML_sz, FSNS(XML_w, XML_val), sSize); |
| } |
| } |
| |
| void DocxAttributeOutput::CharFont( const SvxFontItem& rFont) |
| { |
| GetExport().GetId( rFont ); // ensure font info is written to fontTable.xml |
| const OUString& sFontName(rFont.GetFamilyName()); |
| if (sFontName.isEmpty()) |
| return; |
| |
| if (m_pFontsAttrList && |
| ( m_pFontsAttrList->hasAttribute(FSNS( XML_w, XML_ascii )) || |
| m_pFontsAttrList->hasAttribute(FSNS( XML_w, XML_hAnsi )) ) |
| ) |
| { |
| // tdf#38778: do to fields output into DOC the font could be added before and after field declaration |
| // that all sub runs of the field will have correct font inside. |
| // For DOCX we should do not add the same font information twice in the same node |
| return; |
| } |
| |
| AddToAttrList( m_pFontsAttrList, |
| FSNS( XML_w, XML_ascii ), sFontName, |
| FSNS( XML_w, XML_hAnsi ), sFontName ); |
| } |
| |
| void DocxAttributeOutput::CharFontSize( const SvxFontHeightItem& rFontSize) |
| { |
| OString fontSize = OString::number( ( rFontSize.GetHeight() + 5 ) / 10 ); |
| |
| switch ( rFontSize.Which() ) |
| { |
| case RES_CHRATR_FONTSIZE: |
| case RES_CHRATR_CJK_FONTSIZE: |
| m_pSerializer->singleElementNS(XML_w, XML_sz, FSNS(XML_w, XML_val), fontSize); |
| break; |
| case RES_CHRATR_CTL_FONTSIZE: |
| m_pSerializer->singleElementNS(XML_w, XML_szCs, FSNS(XML_w, XML_val), fontSize); |
| break; |
| } |
| } |
| |
| void DocxAttributeOutput::CharKerning( const SvxKerningItem& rKerning ) |
| { |
| OString aKerning = OString::number( rKerning.GetValue() ); |
| m_pSerializer->singleElementNS(XML_w, XML_spacing, FSNS(XML_w, XML_val), aKerning); |
| } |
| |
| void DocxAttributeOutput::CharLanguage( const SvxLanguageItem& rLanguage ) |
| { |
| OUString aLanguageCode(LanguageTag( rLanguage.GetLanguage()).getBcp47MS()); |
| |
| switch ( rLanguage.Which() ) |
| { |
| case RES_CHRATR_LANGUAGE: |
| AddToAttrList( m_pCharLangAttrList, FSNS( XML_w, XML_val ), aLanguageCode ); |
| break; |
| case RES_CHRATR_CJK_LANGUAGE: |
| AddToAttrList( m_pCharLangAttrList, FSNS( XML_w, XML_eastAsia ), aLanguageCode ); |
| break; |
| case RES_CHRATR_CTL_LANGUAGE: |
| AddToAttrList( m_pCharLangAttrList, FSNS( XML_w, XML_bidi ), aLanguageCode ); |
| break; |
| } |
| } |
| |
| void DocxAttributeOutput::CharPosture( const SvxPostureItem& rPosture ) |
| { |
| if ( rPosture.GetPosture() != ITALIC_NONE ) |
| m_pSerializer->singleElementNS(XML_w, XML_i); |
| else |
| m_pSerializer->singleElementNS(XML_w, XML_i, FSNS(XML_w, XML_val), "false"); |
| } |
| |
| void DocxAttributeOutput::CharShadow( const SvxShadowedItem& rShadow ) |
| { |
| if ( rShadow.GetValue() ) |
| m_pSerializer->singleElementNS(XML_w, XML_shadow); |
| else |
| m_pSerializer->singleElementNS(XML_w, XML_shadow, FSNS(XML_w, XML_val), "false"); |
| } |
| |
| void DocxAttributeOutput::CharUnderline( const SvxUnderlineItem& rUnderline ) |
| { |
| const char *pUnderlineValue; |
| |
| switch ( rUnderline.GetLineStyle() ) |
| { |
| case LINESTYLE_SINGLE: pUnderlineValue = "single"; break; |
| case LINESTYLE_BOLD: pUnderlineValue = "thick"; break; |
| case LINESTYLE_DOUBLE: pUnderlineValue = "double"; break; |
| case LINESTYLE_DOTTED: pUnderlineValue = "dotted"; break; |
| case LINESTYLE_DASH: pUnderlineValue = "dash"; break; |
| case LINESTYLE_DASHDOT: pUnderlineValue = "dotDash"; break; |
| case LINESTYLE_DASHDOTDOT: pUnderlineValue = "dotDotDash"; break; |
| case LINESTYLE_WAVE: pUnderlineValue = "wave"; break; |
| case LINESTYLE_BOLDDOTTED: pUnderlineValue = "dottedHeavy"; break; |
| case LINESTYLE_BOLDDASH: pUnderlineValue = "dashedHeavy"; break; |
| case LINESTYLE_LONGDASH: pUnderlineValue = "dashLongHeavy"; break; |
| case LINESTYLE_BOLDLONGDASH: pUnderlineValue = "dashLongHeavy"; break; |
| case LINESTYLE_BOLDDASHDOT: pUnderlineValue = "dashDotHeavy"; break; |
| case LINESTYLE_BOLDDASHDOTDOT: pUnderlineValue = "dashDotDotHeavy"; break; |
| case LINESTYLE_BOLDWAVE: pUnderlineValue = "wavyHeavy"; break; |
| case LINESTYLE_DOUBLEWAVE: pUnderlineValue = "wavyDouble"; break; |
| case LINESTYLE_NONE: // fall through |
| default: pUnderlineValue = "none"; break; |
| } |
| |
| Color aUnderlineColor = rUnderline.GetColor(); |
| bool bUnderlineHasColor = !aUnderlineColor.IsTransparent(); |
| if (bUnderlineHasColor) |
| { |
| model::ComplexColor const& rComplexColor = rUnderline.getComplexColor(); |
| // Underline has a color |
| rtl::Reference<FastAttributeList> pAttrList = FastSerializerHelper::createAttrList(); |
| pAttrList->add(FSNS(XML_w, XML_val), pUnderlineValue); |
| pAttrList->add(FSNS(XML_w, XML_color), msfilter::util::ConvertColor(aUnderlineColor)); |
| lclAddThemeColorAttributes(pAttrList, rComplexColor); |
| m_pSerializer->singleElementNS(XML_w, XML_u, pAttrList); |
| |
| } |
| else |
| { |
| // Underline has no color |
| m_pSerializer->singleElementNS(XML_w, XML_u, FSNS(XML_w, XML_val), pUnderlineValue); |
| } |
| } |
| |
| void DocxAttributeOutput::CharWeight( const SvxWeightItem& rWeight ) |
| { |
| if ( rWeight.GetWeight() == WEIGHT_BOLD ) |
| m_pSerializer->singleElementNS(XML_w, XML_b); |
| else |
| m_pSerializer->singleElementNS(XML_w, XML_b, FSNS(XML_w, XML_val), "false"); |
| } |
| |
| void DocxAttributeOutput::CharAutoKern( const SvxAutoKernItem& rAutoKern ) |
| { |
| // auto kerning is bound to a minimum font size in Word - but is just a boolean in Writer :-( |
| // 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.) |
| const OString sFontSize = OString::number( static_cast<sal_uInt32>(rAutoKern.GetValue()) * 2 ); |
| m_pSerializer->singleElementNS(XML_w, XML_kern, FSNS(XML_w, XML_val), sFontSize); |
| } |
| |
| void DocxAttributeOutput::CharAnimatedText( const SvxBlinkItem& rBlink ) |
| { |
| if ( rBlink.GetValue() ) |
| m_pSerializer->singleElementNS(XML_w, XML_effect, FSNS(XML_w, XML_val), "blinkBackground"); |
| else |
| m_pSerializer->singleElementNS(XML_w, XML_effect, FSNS(XML_w, XML_val), "none"); |
| } |
| |
| constexpr OUStringLiteral MSWORD_CH_SHADING_FILL = u"FFFFFF"; // The attribute w:fill of w:shd, for MS-Word's character shading, |
| constexpr OUStringLiteral MSWORD_CH_SHADING_COLOR = u"auto"; // The attribute w:color of w:shd, for MS-Word's character shading, |
| constexpr OUStringLiteral MSWORD_CH_SHADING_VAL = u"pct15"; // The attribute w:value of w:shd, for MS-Word's character shading, |
| |
| void DocxAttributeOutput::CharBackground( const SvxBrushItem& rBrush ) |
| { |
| // Check if the brush shading pattern is 'PCT15'. If so - write it back to the DOCX |
| if (rBrush.GetShadingValue() == ShadingPattern::PCT15) |
| { |
| m_pSerializer->singleElementNS( XML_w, XML_shd, |
| FSNS( XML_w, XML_val ), MSWORD_CH_SHADING_VAL, |
| FSNS( XML_w, XML_color ), MSWORD_CH_SHADING_COLOR, |
| FSNS( XML_w, XML_fill ), MSWORD_CH_SHADING_FILL ); |
| } |
| else |
| { |
| m_pSerializer->singleElementNS( XML_w, XML_shd, |
| FSNS( XML_w, XML_fill ), msfilter::util::ConvertColor(rBrush.GetColor()), |
| FSNS( XML_w, XML_val ), "clear" ); |
| } |
| } |
| |
| void DocxAttributeOutput::CharFontCJK( const SvxFontItem& rFont ) |
| { |
| if (m_pFontsAttrList && m_pFontsAttrList->hasAttribute(FSNS(XML_w, XML_eastAsia))) |
| { |
| // tdf#38778: do to fields output into DOC the font could be added before and after field declaration |
| // that all sub runs of the field will have correct font inside. |
| // For DOCX we should do not add the same font information twice in the same node |
| return; |
| } |
| |
| AddToAttrList( m_pFontsAttrList, FSNS( XML_w, XML_eastAsia ), rFont.GetFamilyName() ); |
| } |
| |
| void DocxAttributeOutput::CharPostureCJK( const SvxPostureItem& rPosture ) |
| { |
| if ( rPosture.GetPosture() != ITALIC_NONE ) |
| m_pSerializer->singleElementNS(XML_w, XML_i); |
| else |
| m_pSerializer->singleElementNS(XML_w, XML_i, FSNS(XML_w, XML_val), "false"); |
| } |
| |
| void DocxAttributeOutput::CharWeightCJK( const SvxWeightItem& rWeight ) |
| { |
| if ( rWeight.GetWeight() == WEIGHT_BOLD ) |
| m_pSerializer->singleElementNS(XML_w, XML_b); |
| else |
| m_pSerializer->singleElementNS(XML_w, XML_b, FSNS(XML_w, XML_val), "false"); |
| } |
| |
| void DocxAttributeOutput::CharFontCTL( const SvxFontItem& rFont ) |
| { |
| if (m_pFontsAttrList && m_pFontsAttrList->hasAttribute(FSNS(XML_w, XML_cs))) |
| { |
| // tdf#38778: do to fields output into DOC the font could be added before and after field declaration |
| // that all sub runs of the field will have correct font inside. |
| // For DOCX we should do not add the same font information twice in the same node |
| return; |
| } |
| |
| AddToAttrList( m_pFontsAttrList, FSNS( XML_w, XML_cs ), rFont.GetFamilyName() ); |
| } |
| |
| void DocxAttributeOutput::CharPostureCTL( const SvxPostureItem& rPosture) |
| { |
| if ( rPosture.GetPosture() != ITALIC_NONE ) |
| m_pSerializer->singleElementNS(XML_w, XML_iCs); |
| else |
| m_pSerializer->singleElementNS(XML_w, XML_iCs, FSNS(XML_w, XML_val), "false"); |
| } |
| |
| void DocxAttributeOutput::CharWeightCTL( const SvxWeightItem& rWeight ) |
| { |
| if ( rWeight.GetWeight() == WEIGHT_BOLD ) |
| m_pSerializer->singleElementNS(XML_w, XML_bCs); |
| else |
| m_pSerializer->singleElementNS(XML_w, XML_bCs, FSNS(XML_w, XML_val), "false"); |
| } |
| |
| void DocxAttributeOutput::CharBidiRTL( const SfxPoolItem& ) |
| { |
| } |
| |
| void DocxAttributeOutput::CharIdctHint( const SfxPoolItem& ) |
| { |
| } |
| |
| void DocxAttributeOutput::CharRotate( const SvxCharRotateItem& rRotate) |
| { |
| // Not rotated? |
| if ( !rRotate.GetValue()) |
| return; |
| |
| AddToAttrList( m_pEastAsianLayoutAttrList, FSNS( XML_w, XML_vert ), "true" ); |
| |
| if (rRotate.IsFitToLine()) |
| AddToAttrList( m_pEastAsianLayoutAttrList, FSNS( XML_w, XML_vertCompress ), "true" ); |
| } |
| |
| void DocxAttributeOutput::CharEmphasisMark( const SvxEmphasisMarkItem& rEmphasisMark ) |
| { |
| const char *pEmphasis; |
| const FontEmphasisMark v = rEmphasisMark.GetEmphasisMark(); |
| |
| if (v == (FontEmphasisMark::Dot | FontEmphasisMark::PosAbove)) |
| pEmphasis = "dot"; |
| else if (v == (FontEmphasisMark::Accent | FontEmphasisMark::PosAbove)) |
| pEmphasis = "comma"; |
| else if (v == (FontEmphasisMark::Circle | FontEmphasisMark::PosAbove)) |
| pEmphasis = "circle"; |
| else if (v == (FontEmphasisMark::Dot|FontEmphasisMark::PosBelow)) |
| pEmphasis = "underDot"; |
| else |
| pEmphasis = "none"; |
| |
| m_pSerializer->singleElementNS(XML_w, XML_em, FSNS(XML_w, XML_val), pEmphasis); |
| } |
| |
| void DocxAttributeOutput::CharTwoLines( const SvxTwoLinesItem& rTwoLines ) |
| { |
| if ( !rTwoLines.GetValue() ) |
| return; |
| |
| AddToAttrList( m_pEastAsianLayoutAttrList, FSNS( XML_w, XML_combine ), "true" ); |
| |
| sal_Unicode cStart = rTwoLines.GetStartBracket(); |
| sal_Unicode cEnd = rTwoLines.GetEndBracket(); |
| |
| if (!cStart && !cEnd) |
| return; |
| |
| std::string_view sBracket; |
| if ((cStart == '{') || (cEnd == '}')) |
| sBracket = "curly"; |
| else if ((cStart == '<') || (cEnd == '>')) |
| sBracket = "angle"; |
| else if ((cStart == '[') || (cEnd == ']')) |
| sBracket = "square"; |
| else |
| sBracket = "round"; |
| AddToAttrList( m_pEastAsianLayoutAttrList, FSNS( XML_w, XML_combineBrackets ), sBracket ); |
| } |
| |
| void DocxAttributeOutput::CharScaleWidth( const SvxCharScaleWidthItem& rScaleWidth ) |
| { |
| // Clamp CharScaleWidth to OOXML limits ([1..600]) |
| const sal_Int16 nScaleWidth( std::max<sal_Int16>( 1, |
| std::min<sal_Int16>( rScaleWidth.GetValue(), 600 ) ) ); |
| m_pSerializer->singleElementNS( XML_w, XML_w, |
| FSNS( XML_w, XML_val ), OString::number(nScaleWidth) ); |
| } |
| |
| void DocxAttributeOutput::CharRelief( const SvxCharReliefItem& rRelief ) |
| { |
| switch ( rRelief.GetValue() ) |
| { |
| case FontRelief::Embossed: |
| m_pSerializer->singleElementNS(XML_w, XML_emboss); |
| break; |
| case FontRelief::Engraved: |
| m_pSerializer->singleElementNS(XML_w, XML_imprint); |
| break; |
| default: |
| m_pSerializer->singleElementNS(XML_w, XML_emboss, FSNS(XML_w, XML_val), "false"); |
| m_pSerializer->singleElementNS(XML_w, XML_imprint, FSNS(XML_w, XML_val), "false"); |
| break; |
| } |
| } |
| |
| void DocxAttributeOutput::CharHidden( const SvxCharHiddenItem& rHidden ) |
| { |
| if ( rHidden.GetValue() ) |
| m_pSerializer->singleElementNS(XML_w, XML_vanish); |
| else |
| m_pSerializer->singleElementNS(XML_w, XML_vanish, FSNS(XML_w, XML_val), "false"); |
| } |
| |
| void DocxAttributeOutput::CharBorder( |
| const SvxBorderLine* pAllBorder, const sal_uInt16 nDist, const bool bShadow ) |
| { |
| css::table::BorderLine2 rStyleBorder; |
| const SvxBoxItem* pInherited = nullptr; |
| if ( GetExport().m_bStyDef && GetExport().m_pCurrentStyle && GetExport().m_pCurrentStyle->DerivedFrom() ) |
| pInherited = GetExport().m_pCurrentStyle->DerivedFrom()->GetAttrSet().GetItem<SvxBoxItem>(RES_CHRATR_BOX); |
| else if ( m_rExport.m_pChpIter ) // incredibly undocumented, but this is the character-style info, right? |
| { |
| if (const SvxBoxItem* pPoolItem = GetExport().m_pChpIter->HasTextItem(RES_CHRATR_BOX)) |
| { |
| pInherited = pPoolItem; |
| } |
| } |
| |
| if ( pInherited ) |
| rStyleBorder = SvxBoxItem::SvxLineToLine(pInherited->GetRight(), false); |
| |
| impl_borderLine( m_pSerializer, XML_bdr, pAllBorder, nDist, bShadow, &rStyleBorder ); |
| } |
| |
| void DocxAttributeOutput::CharHighlight( const SvxBrushItem& rHighlight ) |
| { |
| const OString sColor = TransHighlightColor( msfilter::util::TransColToIco(rHighlight.GetColor()) ); |
| if ( !sColor.isEmpty() ) |
| { |
| m_pSerializer->singleElementNS(XML_w, XML_highlight, FSNS(XML_w, XML_val), sColor); |
| } |
| } |
| |
| void DocxAttributeOutput::TextINetFormat( const SwFormatINetFormat& rLink ) |
| { |
| const SwCharFormat* pFormat = m_rExport.m_rDoc.FindCharFormatByName(rLink.GetINetFormat()); |
| if (pFormat) |
| { |
| OString aStyleId(m_rExport.m_pStyles->GetStyleId(m_rExport.GetId(pFormat))); |
| if (!aStyleId.equalsIgnoreAsciiCase("DefaultStyle")) |
| m_pSerializer->singleElementNS(XML_w, XML_rStyle, FSNS(XML_w, XML_val), aStyleId); |
| } |
| } |
| |
| void DocxAttributeOutput::TextCharFormat( const SwFormatCharFormat& rCharFormat ) |
| { |
| OString aStyleId(m_rExport.m_pStyles->GetStyleId(m_rExport.GetId(rCharFormat.GetCharFormat()))); |
| |
| m_pSerializer->singleElementNS(XML_w, XML_rStyle, FSNS(XML_w, XML_val), aStyleId); |
| } |
| |
| void DocxAttributeOutput::RefField( const SwField& rField, const OUString& rRef ) |
| { |
| SwFieldIds nType = rField.GetTyp( )->Which( ); |
| if ( nType == SwFieldIds::GetExp ) |
| { |
| OUString sCmd = FieldString( ww::eREF ) + |
| "\"" + rRef + "\" "; |
| |
| m_rExport.OutputField( &rField, ww::eREF, sCmd ); |
| } |
| |
| // There is nothing to do here for the set fields |
| } |
| |
| void DocxAttributeOutput::HiddenField(const SwField& /*rField*/) |
| { |
| SAL_INFO("sw.ww8", "TODO DocxAttributeOutput::HiddenField()" ); |
| } |
| |
| void DocxAttributeOutput::PostitField( const SwField* pField ) |
| { |
| assert( dynamic_cast< const SwPostItField* >( pField )); |
| const SwPostItField* pPostItField = static_cast<const SwPostItField*>(pField); |
| sal_Int32 nId = 0; |
| auto it = m_rOpenedAnnotationMarksIds.find(pPostItField->GetName()); |
| if (it != m_rOpenedAnnotationMarksIds.end()) |
| // If the postit field has an annotation mark associated, we already have an id. |
| nId = it->second; |
| else |
| // Otherwise get a new one. |
| nId = m_nNextAnnotationMarkId++; |
| m_postitFields.emplace_back(pPostItField, PostItDOCXData{ nId }); |
| } |
| |
| void DocxAttributeOutput::WritePostitFieldReference() |
| { |
| while( m_postitFieldsMaxId < m_postitFields.size()) |
| { |
| OString idstr = OString::number(m_postitFields[m_postitFieldsMaxId].second.id); |
| |
| // In case this file is inside annotation marks, we want to write the |
| // comment reference after the annotation mark is closed, not here. |
| const OUString& idname = m_postitFields[m_postitFieldsMaxId].first->GetName(); |
| auto it = m_rOpenedAnnotationMarksIds.find( idname ); |
| if ( it == m_rOpenedAnnotationMarksIds.end( ) ) |
| m_pSerializer->singleElementNS(XML_w, XML_commentReference, FSNS(XML_w, XML_id), idstr); |
| ++m_postitFieldsMaxId; |
| } |
| } |
| |
| DocxAttributeOutput::hasProperties DocxAttributeOutput::WritePostitFields() |
| { |
| bool bRemovePersonalInfo = SvtSecurityOptions::IsOptionSet( |
| SvtSecurityOptions::EOption::DocWarnRemovePersonalInfo ) && !SvtSecurityOptions::IsOptionSet( |
| SvtSecurityOptions::EOption::DocWarnKeepRedlineInfo); |
| |
| hasProperties eResult = hasProperties::no; |
| for (auto& [f1, data1] : m_postitFields) |
| { |
| if (f1->GetParentId() != 0) |
| { |
| for (size_t i = 0; i < m_postitFields.size(); i++) |
| { |
| auto& [f2, data2] = m_postitFields[i]; |
| if (f2->GetParaId() == f1->GetParentId()) |
| { |
| data2.parentStatus = ParentStatus::IsParent; |
| data1.parentStatus = ParentStatus::HasParent; |
| data1.parentIndex = i; |
| break; |
| } |
| } |
| } |
| } |
| for (auto& [f, data] : m_postitFields) |
| { |
| const DateTime aDateTime = f->GetDateTime(); |
| bool bNoDate = bRemovePersonalInfo || |
| ( aDateTime.GetYear() == 1970 && aDateTime.GetMonth() == 1 && aDateTime.GetDay() == 1 ); |
| |
| rtl::Reference<sax_fastparser::FastAttributeList> pAttributeList |
| = sax_fastparser::FastSerializerHelper::createAttrList(); |
| |
| pAttributeList->add(FSNS( XML_w, XML_id ), OString::number(data.id)); |
| pAttributeList->add(FSNS( XML_w, XML_author ), bRemovePersonalInfo |
| ? "Author" + OString::number( GetExport().GetInfoID(f->GetPar1()) ) |
| : f->GetPar1().toUtf8()); |
| if (!bNoDate) |
| pAttributeList->add(FSNS( XML_w, XML_date ), DateTimeToOString( aDateTime )); |
| pAttributeList->add(FSNS( XML_w, XML_initials ), bRemovePersonalInfo |
| ? OString::number( GetExport().GetInfoID(f->GetInitials()) ) |
| : f->GetInitials().toUtf8()); |
| m_pSerializer->startElementNS( XML_w, XML_comment, pAttributeList ); |
| |
| // Make sure to give parent/child fields a paraId |
| const bool bNeedParaId = f->GetResolved() || data.parentStatus != ParentStatus::None; |
| if (bNeedParaId) |
| eResult = hasProperties::yes; |
| |
| if (f->GetTextObject() != nullptr) |
| { |
| // richtext |
| data.lastParaId = GetExport().WriteOutliner(*f->GetTextObject(), TXT_ATN, bNeedParaId); |
| } |
| else |
| { |
| // just plain text - eg. when the field was created via the |
| // .uno:InsertAnnotation API |
| std::optional<OUString> aParaId; |
| if (bNeedParaId) |
| { |
| data.lastParaId = m_nNextParaId++; |
| aParaId = NumberToHexBinary(data.lastParaId); |
| } |
| m_pSerializer->startElementNS(XML_w, XML_p, FSNS(XML_w14, XML_paraId), aParaId); |
| m_pSerializer->startElementNS(XML_w, XML_r); |
| RunText(f->GetText()); |
| m_pSerializer->endElementNS(XML_w, XML_r); |
| m_pSerializer->endElementNS(XML_w, XML_p); |
| } |
| |
| m_pSerializer->endElementNS( XML_w, XML_comment ); |
| } |
| return eResult; |
| } |
| |
| void DocxAttributeOutput::WritePostItFieldsResolved() |
| { |
| for (auto& [f, data] : m_postitFields) |
| { |
| // Parent fields don't need to be exported here if they don't have a resolved attribute |
| if (!f->GetResolved() && data.parentStatus != ParentStatus::HasParent) |
| continue; |
| OUString idstr = NumberToHexBinary(data.lastParaId); |
| std::optional<OUString> sDone, sParentId; |
| if (f->GetParentId() != 0) |
| { |
| if (data.parentStatus == ParentStatus::HasParent) |
| { |
| // Since parent fields have been resolved first, they should already have an id |
| const PostItDOCXData& aParentFieldData = m_postitFields[data.parentIndex].second; |
| sParentId = NumberToHexBinary(aParentFieldData.lastParaId); |
| } |
| else |
| { |
| SAL_WARN("sw.ww8", "SwPostItField has a parent id, but a matching parent was not found"); |
| } |
| } |
| if (f->GetResolved()) |
| sDone = "1"; |
| m_pSerializer->singleElementNS(XML_w15, XML_commentEx, |
| FSNS(XML_w15, XML_paraId), idstr, |
| FSNS(XML_w15, XML_done), sDone, |
| FSNS(XML_w15, XML_paraIdParent), sParentId); |
| } |
| } |
| |
| bool DocxAttributeOutput::DropdownField( const SwField* pField ) |
| { |
| ww::eField eType = ww::eFORMDROPDOWN; |
| OUString sCmd = FieldString( eType ); |
| GetExport( ).OutputField( pField, eType, sCmd ); |
| |
| return false; |
| } |
| |
| bool DocxAttributeOutput::PlaceholderField( const SwField* pField ) |
| { |
| assert( m_PendingPlaceholder == nullptr ); |
| m_PendingPlaceholder = pField; |
| return false; // do not expand |
| } |
| |
| void DocxAttributeOutput::WritePendingPlaceholder() |
| { |
| if( m_PendingPlaceholder == nullptr ) |
| return; |
| const SwField* pField = m_PendingPlaceholder; |
| m_PendingPlaceholder = nullptr; |
| m_pSerializer->startElementNS(XML_w, XML_sdt); |
| m_pSerializer->startElementNS(XML_w, XML_sdtPr); |
| if( !pField->GetPar2().isEmpty()) |
| m_pSerializer->singleElementNS(XML_w, XML_alias, FSNS(XML_w, XML_val), pField->GetPar2()); |
| m_pSerializer->singleElementNS(XML_w, XML_temporary); |
| m_pSerializer->singleElementNS(XML_w, XML_showingPlcHdr); |
| m_pSerializer->singleElementNS(XML_w, XML_text); |
| m_pSerializer->endElementNS( XML_w, XML_sdtPr ); |
| m_pSerializer->startElementNS(XML_w, XML_sdtContent); |
| m_pSerializer->startElementNS(XML_w, XML_r); |
| RunText( pField->GetPar1()); |
| m_pSerializer->endElementNS( XML_w, XML_r ); |
| m_pSerializer->endElementNS( XML_w, XML_sdtContent ); |
| m_pSerializer->endElementNS( XML_w, XML_sdt ); |
| } |
| |
| void DocxAttributeOutput::SetField( const SwField& rField, ww::eField eType, const OUString& rCmd ) |
| { |
| // field bookmarks are handled in the EndRun method |
| GetExport().OutputField(&rField, eType, rCmd ); |
| } |
| |
| void DocxAttributeOutput::WriteExpand( const SwField* pField ) |
| { |
| // Will be written in the next End Run |
| m_rExport.OutputField( pField, ww::eUNKNOWN, OUString() ); |
| } |
| |
| void DocxAttributeOutput::WriteField_Impl(const SwField *const pField, |
| ww::eField const eType, const OUString& rFieldCmd, FieldFlags const nMode, |
| OUString const*const pBookmarkName) |
| { |
| if (m_bPreventDoubleFieldsHandling) |
| return; |
| |
| struct FieldInfos infos; |
| if (pField) |
| infos.pField = pField->CopyField(); |
| infos.sCmd = rFieldCmd; |
| infos.eType = eType; |
| infos.bClose = bool(FieldFlags::Close & nMode); |
| infos.bSep = bool(FieldFlags::CmdEnd & nMode); |
| infos.bOpen = bool(FieldFlags::Start & nMode); |
| m_Fields.push_back( infos ); |
| |
| if (pBookmarkName) |
| { |
| m_sFieldBkm = *pBookmarkName; |
| } |
| |
| if ( !pField ) |
| return; |
| |
| SwFieldIds nType = pField->GetTyp( )->Which( ); |
| sal_uInt16 nSubType = pField->GetSubType(); |
| |
| // TODO Any other field types here ? |
| if ( ( nType == SwFieldIds::SetExp ) && ( nSubType & nsSwGetSetExpType::GSE_STRING ) ) |
| { |
| const SwSetExpField *pSet = static_cast<const SwSetExpField*>( pField ); |
| m_sFieldBkm = pSet->GetPar1( ); |
| } |
| else if ( nType == SwFieldIds::Dropdown ) |
| { |
| const SwDropDownField* pDropDown = static_cast<const SwDropDownField*>( pField ); |
| m_sFieldBkm = pDropDown->GetName( ); |
| } |
| } |
| |
| void DocxAttributeOutput::WriteFormData_Impl( const ::sw::mark::IFieldmark& rFieldmark ) |
| { |
| if ( !m_Fields.empty() ) |
| m_Fields.begin()->pFieldmark = &rFieldmark; |
| } |
| |
| void DocxAttributeOutput::WriteBookmarks_Impl( std::vector< OUString >& rStarts, std::vector< OUString >& rEnds, const SwRedlineData* pRedlineData ) |
| { |
| for ( const OUString & name : rStarts ) |
| { |
| if (name.startsWith("permission-for-group:") || |
| name.startsWith("permission-for-user:")) |
| { |
| m_rPermissionsStart.push_back(name); |
| } |
| else |
| { |
| m_rBookmarksStart.push_back(name); |
| m_pMoveRedlineData = const_cast<SwRedlineData*>(pRedlineData); |
| } |
| } |
| rStarts.clear(); |
| |
| for ( const OUString & name : rEnds ) |
| { |
| if (name.startsWith("permission-for-group:") || |
| name.startsWith("permission-for-user:")) |
| { |
| m_rPermissionsEnd.push_back(name); |
| } |
| else |
| { |
| m_rBookmarksEnd.push_back(name); |
| } |
| } |
| rEnds.clear(); |
| } |
| |
| void DocxAttributeOutput::WriteFinalBookmarks_Impl( std::vector< OUString >& rStarts, std::vector< OUString >& rEnds ) |
| { |
| for ( const OUString & name : rStarts ) |
| { |
| if (name.startsWith("permission-for-group:") || |
| name.startsWith("permission-for-user:")) |
| { |
| m_rPermissionsStart.push_back(name); |
| } |
| else |
| { |
| m_rFinalBookmarksStart.push_back(name); |
| } |
| } |
| rStarts.clear(); |
| |
| for ( const OUString & name : rEnds ) |
| { |
| if (name.startsWith("permission-for-group:") || |
| name.startsWith("permission-for-user:")) |
| { |
| m_rPermissionsEnd.push_back(name); |
| } |
| else |
| { |
| m_rFinalBookmarksEnd.push_back(name); |
| } |
| } |
| rEnds.clear(); |
| } |
| |
| void DocxAttributeOutput::WriteAnnotationMarks_Impl( std::vector< OUString >& rStarts, |
| std::vector< OUString >& rEnds ) |
| { |
| m_rAnnotationMarksStart.insert(m_rAnnotationMarksStart.end(), rStarts.begin(), rStarts.end()); |
| rStarts.clear(); |
| |
| m_rAnnotationMarksEnd.insert(m_rAnnotationMarksEnd.end(), rEnds.begin(), rEnds.end()); |
| rEnds.clear(); |
| } |
| |
| void DocxAttributeOutput::TextFootnote_Impl( const SwFormatFootnote& rFootnote ) |
| { |
| const SwEndNoteInfo& rInfo = rFootnote.IsEndNote()? |
| m_rExport.m_rDoc.GetEndNoteInfo(): m_rExport.m_rDoc.GetFootnoteInfo(); |
| |
| // footnote/endnote run properties |
| const SwCharFormat* pCharFormat = rInfo.GetAnchorCharFormat( m_rExport.m_rDoc ); |
| |
| OString aStyleId(m_rExport.m_pStyles->GetStyleId(m_rExport.GetId(pCharFormat))); |
| |
| m_pSerializer->singleElementNS(XML_w, XML_rStyle, FSNS(XML_w, XML_val), aStyleId); |
| |
| // remember the footnote/endnote to |
| // 1) write the footnoteReference/endnoteReference in EndRunProperties() |
| // 2) be able to dump them all to footnotes.xml/endnotes.xml |
| if ( !rFootnote.IsEndNote() && m_rExport.m_rDoc.GetFootnoteInfo().m_ePos != FTNPOS_CHAPTER ) |
| m_pFootnotesList->add( rFootnote ); |
| else |
| m_pEndnotesList->add( rFootnote ); |
| } |
| |
| void DocxAttributeOutput::FootnoteEndnoteReference() |
| { |
| sal_Int32 nId; |
| const SwFormatFootnote *pFootnote = m_pFootnotesList->getCurrent( nId ); |
| sal_Int32 nToken = XML_footnoteReference; |
| |
| // both cannot be set at the same time - if they are, it's a bug |
| if ( !pFootnote ) |
| { |
| pFootnote = m_pEndnotesList->getCurrent( nId ); |
| nToken = XML_endnoteReference; |
| } |
| |
| if ( !pFootnote ) |
| return; |
| |
| // write it |
| if ( pFootnote->GetNumStr().isEmpty() ) |
| { |
| // autonumbered |
| m_pSerializer->singleElementNS(XML_w, nToken, FSNS(XML_w, XML_id), OString::number(nId)); |
| } |
| else |
| { |
| // not autonumbered |
| m_pSerializer->singleElementNS( XML_w, nToken, |
| FSNS( XML_w, XML_customMarkFollows ), "1", |
| FSNS( XML_w, XML_id ), OString::number(nId) ); |
| |
| RunText( pFootnote->GetNumStr() ); |
| } |
| } |
| |
| static void WriteFootnoteSeparatorHeight( |
| ::sax_fastparser::FSHelperPtr const& pSerializer, SwTwips const nHeight) |
| { |
| // try to get the height by setting font size of the paragraph |
| if (nHeight != 0) |
| { |
| pSerializer->startElementNS(XML_w, XML_pPr); |
| pSerializer->startElementNS(XML_w, XML_rPr); |
| pSerializer->singleElementNS(XML_w, XML_sz, FSNS(XML_w, XML_val), |
| OString::number((nHeight + 5) / 10)); |
| pSerializer->endElementNS(XML_w, XML_rPr); |
| pSerializer->endElementNS(XML_w, XML_pPr); |
| } |
| } |
| |
| void DocxAttributeOutput::FootnotesEndnotes( bool bFootnotes ) |
| { |
| const FootnotesVector& rVector = bFootnotes? m_pFootnotesList->getVector(): m_pEndnotesList->getVector(); |
| |
| sal_Int32 nBody = bFootnotes? XML_footnotes: XML_endnotes; |
| sal_Int32 nItem = bFootnotes? XML_footnote: XML_endnote; |
| |
| m_pSerializer->startElementNS( XML_w, nBody, m_rExport.MainXmlNamespaces() ); |
| |
| sal_Int32 nIndex = 0; |
| |
| // separator |
| // note: can only be defined for the whole document, not per section |
| m_pSerializer->startElementNS( XML_w, nItem, |
| FSNS( XML_w, XML_id ), OString::number(nIndex++), |
| FSNS( XML_w, XML_type ), "separator" ); |
| m_pSerializer->startElementNS(XML_w, XML_p); |
| |
| bool bSeparator = true; |
| SwTwips nHeight(0); |
| if (bFootnotes) |
| { |
| const SwPageFootnoteInfo& rFootnoteInfo = m_rExport.m_rDoc.GetPageDesc(0).GetFootnoteInfo(); |
| // Request separator only if both width and thickness are non-zero. |
| bSeparator = rFootnoteInfo.GetLineStyle() != SvxBorderLineStyle::NONE |
| && rFootnoteInfo.GetLineWidth() > 0 |
| && double(rFootnoteInfo.GetWidth()) > 0; |
| nHeight = sw::FootnoteSeparatorHeight(m_rExport.m_rDoc, rFootnoteInfo); |
| |
| const IDocumentSettingAccess& rIDSA = m_rExport.m_rDoc.getIDocumentSettingAccess(); |
| if (rIDSA.get(DocumentSettingId::CONTINUOUS_ENDNOTES)) |
| { |
| // Don't request separator if this is a Word-style separator, which is handled at a |
| // layout level. |
| nHeight = 0; |
| } |
| } |
| |
| WriteFootnoteSeparatorHeight(m_pSerializer, nHeight); |
| |
| m_pSerializer->startElementNS(XML_w, XML_r); |
| if (bSeparator) |
| m_pSerializer->singleElementNS(XML_w, XML_separator); |
| m_pSerializer->endElementNS( XML_w, XML_r ); |
| m_pSerializer->endElementNS( XML_w, XML_p ); |
| m_pSerializer->endElementNS( XML_w, nItem ); |
| |
| // separator |
| m_pSerializer->startElementNS( XML_w, nItem, |
| FSNS( XML_w, XML_id ), OString::number(nIndex++), |
| FSNS( XML_w, XML_type ), "continuationSeparator" ); |
| m_pSerializer->startElementNS(XML_w, XML_p); |
| |
| WriteFootnoteSeparatorHeight(m_pSerializer, nHeight); |
| |
| m_pSerializer->startElementNS(XML_w, XML_r); |
| if (bSeparator) |
| { |
| m_pSerializer->singleElementNS(XML_w, XML_continuationSeparator); |
| } |
| m_pSerializer->endElementNS( XML_w, XML_r ); |
| m_pSerializer->endElementNS( XML_w, XML_p ); |
| m_pSerializer->endElementNS( XML_w, nItem ); |
| |
| // if new special ones are added, update also WriteFootnoteEndnotePr() |
| |
| // footnotes/endnotes themselves |
| for ( const auto& rpItem : rVector ) |
| { |
| m_footnoteEndnoteRefTag = bFootnotes ? XML_footnoteRef : XML_endnoteRef; |
| m_footnoteCustomLabel = rpItem->GetNumStr(); |
| |
| m_pSerializer->startElementNS(XML_w, nItem, FSNS(XML_w, XML_id), OString::number(nIndex)); |
| |
| const SwNodeIndex* pIndex = rpItem->GetTextFootnote()->GetStartNode(); |
| m_rExport.WriteSpecialText( pIndex->GetIndex() + 1, |
| pIndex->GetNode().EndOfSectionIndex(), |
| bFootnotes? TXT_FTN: TXT_EDN ); |
| |
| m_pSerializer->endElementNS( XML_w, nItem ); |
| ++nIndex; |
| } |
| |
| m_pSerializer->endElementNS( XML_w, nBody ); |
| |
| } |
| |
| void DocxAttributeOutput::WriteFootnoteEndnotePr( ::sax_fastparser::FSHelperPtr const & fs, int tag, |
| const SwEndNoteInfo& info, int listtag ) |
| { |
| fs->startElementNS(XML_w, tag); |
| |
| SwSectionFormats& rSections = m_rExport.m_rDoc.GetSections(); |
| if (!rSections.empty()) |
| { |
| SwSectionFormat* pFormat = rSections[0]; |
| bool bEndnAtEnd = pFormat->GetEndAtTextEnd().IsAtEnd(); |
| if (bEndnAtEnd) |
| { |
| fs->singleElementNS(XML_w, XML_pos, FSNS(XML_w, XML_val), "sectEnd"); |
| } |
| } |
| |
| OString aCustomFormat; |
| OString fmt = lcl_ConvertNumberingType(info.m_aFormat.GetNumberingType(), nullptr, aCustomFormat); |
| if (!fmt.isEmpty() && aCustomFormat.isEmpty()) |
| fs->singleElementNS(XML_w, XML_numFmt, FSNS(XML_w, XML_val), fmt); |
| if( info.m_nFootnoteOffset != 0 ) |
| fs->singleElementNS( XML_w, XML_numStart, FSNS( XML_w, XML_val ), |
| OString::number(info.m_nFootnoteOffset + 1) ); |
| |
| const SwFootnoteInfo* pFootnoteInfo = dynamic_cast<const SwFootnoteInfo*>(&info); |
| if( pFootnoteInfo ) |
| { |
| switch( pFootnoteInfo->m_eNum ) |
| { |
| case FTNNUM_PAGE: fmt = "eachPage"_ostr; break; |
| case FTNNUM_CHAPTER: fmt = "eachSect"_ostr; break; |
| default: fmt.clear(); break; |
| } |
| if (!fmt.isEmpty()) |
| fs->singleElementNS(XML_w, XML_numRestart, FSNS(XML_w, XML_val), fmt); |
| } |
| |
| if( listtag != 0 ) // we are writing to settings.xml, write also special footnote/endnote list |
| { // there are currently only two hardcoded ones ( see FootnotesEndnotes()) |
| fs->singleElementNS(XML_w, listtag, FSNS(XML_w, XML_id), "0"); |
| fs->singleElementNS(XML_w, listtag, FSNS(XML_w, XML_id), "1"); |
| } |
| fs->endElementNS( XML_w, tag ); |
| } |
| |
| void DocxAttributeOutput::SectFootnoteEndnotePr() |
| { |
| if( HasFootnotes()) |
| WriteFootnoteEndnotePr( m_pSerializer, XML_footnotePr, m_rExport.m_rDoc.GetFootnoteInfo(), 0 ); |
| if( HasEndnotes()) |
| WriteFootnoteEndnotePr( m_pSerializer, XML_endnotePr, m_rExport.m_rDoc.GetEndNoteInfo(), 0 ); |
| } |
| |
| void DocxAttributeOutput::ParaLineSpacing_Impl( short nSpace, short nMulti ) |
| { |
| if ( nSpace < 0 ) |
| { |
| AddToAttrList( m_pParagraphSpacingAttrList, |
| FSNS( XML_w, XML_lineRule ), "exact", |
| FSNS( XML_w, XML_line ), OString::number( -nSpace ) ); |
| } |
| else if( nSpace > 0 && nMulti ) |
| { |
| AddToAttrList( m_pParagraphSpacingAttrList, |
| FSNS( XML_w, XML_lineRule ), "auto", |
| FSNS( XML_w, XML_line ), OString::number( nSpace ) ); |
| } |
| else |
| { |
| AddToAttrList( m_pParagraphSpacingAttrList, |
| FSNS( XML_w, XML_lineRule ), "atLeast", |
| FSNS( XML_w, XML_line ), OString::number( nSpace ) ); |
| } |
| } |
| |
| void DocxAttributeOutput::ParaAdjust( const SvxAdjustItem& rAdjust ) |
| { |
| const char *pAdjustString; |
| |
| bool const bEcma = GetExport().GetFilter().getVersion() == oox::core::ECMA_376_1ST_EDITION; |
| |
| const SfxItemSet* pItems = GetExport().GetCurItemSet(); |
| const SvxFrameDirectionItem* rFrameDir = pItems? |
| pItems->GetItem( RES_FRAMEDIR ) : nullptr; |
| |
| SvxFrameDirection nDir = SvxFrameDirection::Environment; |
| if( rFrameDir != nullptr ) |
| nDir = rFrameDir->GetValue(); |
| if ( nDir == SvxFrameDirection::Environment ) |
| nDir = GetExport( ).GetDefaultFrameDirection( ); |
| bool bRtl = ( nDir == SvxFrameDirection::Horizontal_RL_TB ); |
| |
| switch ( rAdjust.GetAdjust() ) |
| { |
| case SvxAdjust::Left: |
| if ( bEcma ) |
| { |
| if ( bRtl ) |
| pAdjustString = "right"; |
| else |
| pAdjustString = "left"; |
| } |
| else if ( bRtl ) |
| pAdjustString = "end"; |
| else |
| pAdjustString = "start"; |
| break; |
| case SvxAdjust::Right: |
| if ( bEcma ) |
| { |
| if ( bRtl ) |
| pAdjustString = "left"; |
| else |
| pAdjustString = "right"; |
| } |
| else if ( bRtl ) |
| pAdjustString = "start"; |
| else |
| pAdjustString = "end"; |
| break; |
| case SvxAdjust::BlockLine: |
| case SvxAdjust::Block: |
| if (rAdjust.GetLastBlock() == SvxAdjust::Block) |
| pAdjustString = "distribute"; |
| else |
| pAdjustString = "both"; |
| break; |
| case SvxAdjust::Center: |
| pAdjustString = "center"; |
| break; |
| default: |
| return; // not supported attribute |
| } |
| m_pSerializer->singleElementNS(XML_w, XML_jc, FSNS(XML_w, XML_val), pAdjustString); |
| } |
| |
| void DocxAttributeOutput::ParaSplit( const SvxFormatSplitItem& rSplit ) |
| { |
| if (rSplit.GetValue()) |
| m_pSerializer->singleElementNS(XML_w, XML_keepLines, FSNS(XML_w, XML_val), "false"); |
| else |
| m_pSerializer->singleElementNS(XML_w, XML_keepLines); |
| } |
| |
| void DocxAttributeOutput::ParaWidows( const SvxWidowsItem& rWidows ) |
| { |
| if (rWidows.GetValue()) |
| m_pSerializer->singleElementNS(XML_w, XML_widowControl); |
| else |
| m_pSerializer->singleElementNS(XML_w, XML_widowControl, FSNS(XML_w, XML_val), "false"); |
| } |
| |
| static void impl_WriteTabElement( FSHelperPtr const & pSerializer, |
| const SvxTabStop& rTab, tools::Long tabsOffset ) |
| { |
| rtl::Reference<FastAttributeList> pTabElementAttrList = FastSerializerHelper::createAttrList(); |
| |
| switch (rTab.GetAdjustment()) |
| { |
| case SvxTabAdjust::Right: |
| pTabElementAttrList->add( FSNS( XML_w, XML_val ), "right" ); |
| break; |
| case SvxTabAdjust::Decimal: |
| pTabElementAttrList->add( FSNS( XML_w, XML_val ), "decimal" ); |
| break; |
| case SvxTabAdjust::Center: |
| pTabElementAttrList->add( FSNS( XML_w, XML_val ), "center" ); |
| break; |
| case SvxTabAdjust::Default: |
| case SvxTabAdjust::Left: |
| default: |
| pTabElementAttrList->add( FSNS( XML_w, XML_val ), "left" ); |
| break; |
| } |
| |
| // Write position according to used offset of the whole paragraph. |
| // In DOCX, w:pos specifies the position of the current custom tab stop with respect to the current page margins. |
| // But in ODT, zero position could be page margins or paragraph indent according to used settings. |
| // This is handled outside of this method and provided for us in tabsOffset parameter. |
| pTabElementAttrList->add( FSNS( XML_w, XML_pos ), OString::number( rTab.GetTabPos() + tabsOffset ) ); |
| |
| sal_Unicode cFillChar = rTab.GetFill(); |
| |
| if ('.' == cFillChar ) |
| pTabElementAttrList->add( FSNS( XML_w, XML_leader ), "dot" ); |
| else if ( '-' == cFillChar ) |
| pTabElementAttrList->add( FSNS( XML_w, XML_leader ), "hyphen" ); |
| else if ( u'\x00B7' == cFillChar ) // middle dot |
| pTabElementAttrList->add( FSNS( XML_w, XML_leader ), "middleDot" ); |
| else if ( '_' == cFillChar ) |
| pTabElementAttrList->add( FSNS( XML_w, XML_leader ), "underscore" ); |
| else |
| pTabElementAttrList->add( FSNS( XML_w, XML_leader ), "none" ); |
| |
| pSerializer->singleElementNS(XML_w, XML_tab, pTabElementAttrList); |
| } |
| |
| void DocxAttributeOutput::ParaTabStop( const SvxTabStopItem& rTabStop ) |
| { |
| const SvxTabStopItem* pInheritedTabs = nullptr; |
| if ( GetExport().m_pStyAttr ) |
| pInheritedTabs = GetExport().m_pStyAttr->GetItem<SvxTabStopItem>(RES_PARATR_TABSTOP); |
| else if ( GetExport().m_pCurrentStyle && GetExport().m_pCurrentStyle->DerivedFrom() ) |
| pInheritedTabs = GetExport().m_pCurrentStyle->DerivedFrom()->GetAttrSet().GetItem<SvxTabStopItem>(RES_PARATR_TABSTOP); |
| const sal_uInt16 nInheritedTabCount = pInheritedTabs ? pInheritedTabs->Count() : 0; |
| const sal_uInt16 nCount = rTabStop.Count(); |
| |
| // <w:tabs> must contain at least one <w:tab>, so don't write it empty |
| if ( !nCount && !nInheritedTabCount ) |
| return; |
| if( nCount == 1 && rTabStop[ 0 ].GetAdjustment() == SvxTabAdjust::Default ) |
| { |
| GetExport().setDefaultTabStop( rTabStop[ 0 ].GetTabPos()); |
| return; |
| } |
| |
| // do not output inherited tabs twice (inside styles and inside inline properties) |
| if ( nCount == nInheritedTabCount && nCount > 0 ) |
| { |
| if ( *pInheritedTabs == rTabStop ) |
| return; |
| } |
| |
| m_pSerializer->startElementNS(XML_w, XML_tabs); |
| |
| // Get offset for tabs |
| // In DOCX, w:pos specifies the position of the current custom tab stop with respect to the current page margins. |
| // But in ODT, zero position could be page margins or paragraph indent according to used settings. |
| tools::Long tabsOffset = m_rExport.GetParaTabStopOffset(); |
| |
| // clear unused inherited tabs - otherwise the style will add them back in |
| sal_Int32 nCurrTab = 0; |
| for ( sal_uInt16 i = 0; i < nInheritedTabCount; ++i ) |
| { |
| while ( nCurrTab < nCount && rTabStop[nCurrTab] < pInheritedTabs->At(i) ) |
| ++nCurrTab; |
| |
| if ( nCurrTab == nCount || pInheritedTabs->At(i) < rTabStop[nCurrTab] ) |
| { |
| m_pSerializer->singleElementNS( XML_w, XML_tab, |
| FSNS( XML_w, XML_val ), "clear", |
| FSNS( XML_w, XML_pos ), OString::number(pInheritedTabs->At(i).GetTabPos()) ); |
| } |
| } |
| |
| for (sal_uInt16 i = 0; i < nCount; i++ ) |
| { |
| if( rTabStop[i].GetAdjustment() != SvxTabAdjust::Default ) |
| impl_WriteTabElement( m_pSerializer, rTabStop[i], tabsOffset ); |
| else |
| GetExport().setDefaultTabStop( rTabStop[i].GetTabPos()); |
| } |
| |
| m_pSerializer->endElementNS( XML_w, XML_tabs ); |
| } |
| |
| void DocxAttributeOutput::ParaHyphenZone( const SvxHyphenZoneItem& rHyphenZone ) |
| { |
| m_pSerializer->singleElementNS( XML_w, XML_suppressAutoHyphens, |
| FSNS( XML_w, XML_val ), OString::boolean( !rHyphenZone.IsHyphen() ) ); |
| } |
| |
| void DocxAttributeOutput::ParaNumRule_Impl( const SwTextNode* pTextNd, sal_Int32 nLvl, sal_Int32 nNumId ) |
| { |
| if ( USHRT_MAX == nNumId ) |
| return; |
| |
| // LibreOffice is not very flexible with "Outline Numbering" (aka "Outline" numbering style). |
| // Only ONE numbering rule ("Outline") can be associated with a style-assigned-listLevel, |
| // and no other style is able to inherit these numId/nLvl settings - only text nodes can. |
| // So listLevel only exists in paragraph properties EXCEPT for up to ten styles that have been |
| // assigned to one of these special Chapter Numbering listlevels (by default Heading 1-10). |
| const sal_Int32 nTableSize = m_rExport.m_pUsedNumTable ? m_rExport.m_pUsedNumTable->size() : 0; |
| const SwNumRule* pRule = nNumId > 0 && nNumId <= nTableSize ? (*m_rExport.m_pUsedNumTable)[nNumId-1] : nullptr; |
| const SwTextFormatColl* pColl = pTextNd ? pTextNd->GetTextColl() : nullptr; |
| // Do not duplicate numbering that is inherited from the (Chapter numbering) style |
| // (since on import we duplicate style numbering/listlevel to the paragraph). |
| if (pColl && pColl->IsAssignedToListLevelOfOutlineStyle() |
| && nLvl == pColl->GetAssignedOutlineStyleLevel() && pRule && pRule->IsOutlineRule()) |
| { |
| // By definition of how LO is implemented, assignToListLevel is only possible |
| // when the style is also using OutlineRule for numbering. Adjust logic if that changes. |
| assert(pRule->GetName() == pColl->GetNumRule(true).GetValue()); |
| return; |
| } |
| |
| m_pSerializer->startElementNS(XML_w, XML_numPr); |
| m_pSerializer->singleElementNS(XML_w, XML_ilvl, FSNS(XML_w, XML_val), OString::number(nLvl)); |
| m_pSerializer->singleElementNS(XML_w, XML_numId, FSNS(XML_w, XML_val), OString::number(nNumId)); |
| m_pSerializer->endElementNS(XML_w, XML_numPr); |
| } |
| |
| void DocxAttributeOutput::ParaScriptSpace( const SfxBoolItem& rScriptSpace ) |
| { |
| m_pSerializer->singleElementNS( XML_w, XML_autoSpaceDE, |
| FSNS( XML_w, XML_val ), OString::boolean( rScriptSpace.GetValue() ) ); |
| } |
| |
| void DocxAttributeOutput::ParaHangingPunctuation( const SfxBoolItem& rItem ) |
| { |
| m_pSerializer->singleElementNS( XML_w, XML_overflowPunct, |
| FSNS( XML_w, XML_val ), OString::boolean( rItem.GetValue() ) ); |
| } |
| |
| void DocxAttributeOutput::ParaForbiddenRules( const SfxBoolItem& rItem ) |
| { |
| m_pSerializer->singleElementNS( XML_w, XML_kinsoku, |
| FSNS( XML_w, XML_val ), OString::boolean( rItem.GetValue() ) ); |
| } |
| |
| void DocxAttributeOutput::ParaVerticalAlign( const SvxParaVertAlignItem& rAlign ) |
| { |
| const char *pAlignString; |
| |
| switch ( rAlign.GetValue() ) |
| { |
| case SvxParaVertAlignItem::Align::Baseline: |
| pAlignString = "baseline"; |
| break; |
| case SvxParaVertAlignItem::Align::Top: |
| pAlignString = "top"; |
| break; |
| case SvxParaVertAlignItem::Align::Center: |
| pAlignString = "center"; |
| break; |
| case SvxParaVertAlignItem::Align::Bottom: |
| pAlignString = "bottom"; |
| break; |
| case SvxParaVertAlignItem::Align::Automatic: |
| pAlignString = "auto"; |
| break; |
| default: |
| return; // not supported attribute |
| } |
| m_pSerializer->singleElementNS(XML_w, XML_textAlignment, FSNS(XML_w, XML_val), pAlignString); |
| } |
| |
| void DocxAttributeOutput::ParaSnapToGrid( const SvxParaGridItem& rGrid ) |
| { |
| m_pSerializer->singleElementNS( XML_w, XML_snapToGrid, |
| FSNS( XML_w, XML_val ), OString::boolean( rGrid.GetValue() ) ); |
| } |
| |
| void DocxAttributeOutput::FormatFrameSize( const SwFormatFrameSize& rSize ) |
| { |
| if (m_rExport.SdrExporter().getTextFrameSyntax() && m_rExport.SdrExporter().getFlyFrameSize()) |
| { |
| const Size* pSize = m_rExport.SdrExporter().getFlyFrameSize(); |
| m_rExport.SdrExporter().getTextFrameStyle().append(";width:" + OString::number(double(pSize->Width()) / 20)); |
| m_rExport.SdrExporter().getTextFrameStyle().append("pt;height:" + OString::number(double(pSize->Height()) / 20) + "pt"); |
| } |
| else if (m_rExport.SdrExporter().getDMLTextFrameSyntax()) |
| { |
| } |
| else if ( m_rExport.m_bOutFlyFrameAttrs ) |
| { |
| if ( rSize.GetWidth() && rSize.GetWidthSizeType() == SwFrameSize::Fixed ) |
| AddToAttrList( m_rExport.SdrExporter().getFlyAttrList(), |
| FSNS( XML_w, XML_w ), OString::number( rSize.GetWidth( ) ) ); |
| |
| if ( rSize.GetHeight() ) |
| { |
| std::string_view sRule( "exact" ); |
| if ( rSize.GetHeightSizeType() == SwFrameSize::Minimum ) |
| sRule = "atLeast"; |
| AddToAttrList( m_rExport.SdrExporter().getFlyAttrList(), |
| FSNS( XML_w, XML_hRule ), sRule, |
| FSNS( XML_w, XML_h ), OString::number( rSize.GetHeight( ) ) ); |
| } |
| } |
| else if ( m_rExport.m_bOutPageDescs ) |
| { |
| rtl::Reference<FastAttributeList> attrList = FastSerializerHelper::createAttrList( ); |
| if ( m_rExport.m_pCurrentPageDesc->GetLandscape( ) ) |
| attrList->add( FSNS( XML_w, XML_orient ), "landscape" ); |
| |
| attrList->add( FSNS( XML_w, XML_w ), OString::number( rSize.GetWidth( ) ) ); |
| attrList->add( FSNS( XML_w, XML_h ), OString::number( rSize.GetHeight( ) ) ); |
| |
| m_pSerializer->singleElementNS( XML_w, XML_pgSz, attrList ); |
| } |
| } |
| |
| void DocxAttributeOutput::FormatPaperBin(const SvxPaperBinItem& rPaperBin) |
| { |
| sal_Int8 nPaperBin = rPaperBin.GetValue(); |
| rtl::Reference<FastAttributeList> attrList = FastSerializerHelper::createAttrList( ); |
| SfxPrinter* pPrinter = m_rExport.m_rDoc.getIDocumentDeviceAccess().getPrinter(true); |
| sal_Int16 nPaperSource = pPrinter->GetSourceIndexByPaperBin(nPaperBin); |
| attrList->add( FSNS( XML_w, XML_first ), OString::number(nPaperSource) ); |
| attrList->add( FSNS( XML_w, XML_other ), OString::number(nPaperSource) ); |
| m_pSerializer->singleElementNS( XML_w, XML_paperSrc, attrList ); |
| } |
| |
| void DocxAttributeOutput::FormatFirstLineIndent(SvxFirstLineIndentItem const& rFirstLine) |
| { |
| sal_Int32 const nFirstLineAdjustment(rFirstLine.GetTextFirstLineOffset()); |
| if (nFirstLineAdjustment > 0) |
| { |
| AddToAttrList(m_pLRSpaceAttrList, FSNS(XML_w, XML_firstLine), |
| OString::number(nFirstLineAdjustment)); |
| } |
| else |
| { |
| AddToAttrList(m_pLRSpaceAttrList, FSNS(XML_w, XML_hanging), |
| OString::number(- nFirstLineAdjustment)); |
| } |
| } |
| |
| void DocxAttributeOutput::FormatTextLeftMargin(SvxTextLeftMarginItem const& rTextLeftMargin) |
| { |
| SvxTextLeftMarginItem const* pTextLeftMargin(&rTextLeftMargin); |
| ::std::optional<SvxTextLeftMarginItem> oCopy; |
| if (dynamic_cast<SwContentNode const*>(GetExport().m_pOutFormatNode) != nullptr) |
| { |
| auto pTextNd(static_cast<SwTextNode const*>(GetExport().m_pOutFormatNode)); |
| // WW doesn't have a concept of a paragraph that's in a list but not |
| // counted in the list - see AttributeOutputBase::ParaNumRule() |
| // forcing non-existent numId="0" in this case. |
| // This means WW won't apply the indents from the numbering, |
| // so try to add them as paragraph properties here. |
| if (!pTextNd->IsCountedInList()) |
| { |
| SfxItemSetFixed<RES_MARGIN_TEXTLEFT, RES_MARGIN_TEXTLEFT> temp(m_rExport.m_rDoc.GetAttrPool()); |
| pTextNd->GetParaAttr(temp, 0, 0, false, true, true, nullptr); |
| if (auto *const pItem = temp.GetItem(RES_MARGIN_TEXTLEFT)) |
| { |
| oCopy.emplace(*pItem); |
| pTextLeftMargin = &*oCopy; |
| } |
| } |
| } |
| bool const bEcma1st(m_rExport.GetFilter().getVersion() == oox::core::ECMA_376_1ST_EDITION); |
| AddToAttrList(m_pLRSpaceAttrList, |
| FSNS(XML_w, (bEcma1st ? XML_left : XML_start)), |
| OString::number(pTextLeftMargin->GetTextLeft())); |
| } |
| |
| void DocxAttributeOutput::FormatRightMargin(SvxRightMarginItem const& rRightMargin) |
| { |
| // (paragraph case, this will be an else branch once others are converted) |
| #if 0 |
| else |
| #endif |
| { |
| bool const bEcma1st(m_rExport.GetFilter().getVersion() == oox::core::ECMA_376_1ST_EDITION); |
| AddToAttrList(m_pLRSpaceAttrList, |
| FSNS(XML_w, (bEcma1st ? XML_right : XML_end)), |
| OString::number(rRightMargin.GetRight())); |
| } |
| } |
| |
| void DocxAttributeOutput::FormatLRSpace( const SvxLRSpaceItem& rLRSpace ) |
| { |
| bool const bEcma = m_rExport.GetFilter().getVersion() == oox::core::ECMA_376_1ST_EDITION; |
| if (m_rExport.SdrExporter().getTextFrameSyntax()) |
| { |
| m_rExport.SdrExporter().getTextFrameStyle().append(";mso-wrap-distance-left:" + OString::number(double(rLRSpace.GetLeft()) / 20) + "pt"); |
| m_rExport.SdrExporter().getTextFrameStyle().append(";mso-wrap-distance-right:" + OString::number(double(rLRSpace.GetRight()) / 20) + "pt"); |
| } |
| else if (m_rExport.SdrExporter().getDMLTextFrameSyntax()) |
| { |
| } |
| else if ( m_rExport.m_bOutFlyFrameAttrs ) |
| { |
| AddToAttrList( m_rExport.SdrExporter().getFlyAttrList(), FSNS( XML_w, XML_hSpace ), |
| OString::number( |
| ( rLRSpace.GetLeft() + rLRSpace.GetRight() ) / 2 ) ); |
| } |
| else if ( m_rExport.m_bOutPageDescs ) |
| { |
| m_pageMargins.nLeft = 0; |
| m_pageMargins.nRight = 0; |
| |
| const SvxBoxItem* pBoxItem = m_rExport.HasItem(RES_BOX); |
| if (pBoxItem) |
| { |
| m_pageMargins.nLeft = pBoxItem->CalcLineSpace( SvxBoxItemLine::LEFT, /*bEvenIfNoLine*/true ); |
| m_pageMargins.nRight = pBoxItem->CalcLineSpace( SvxBoxItemLine::RIGHT, /*bEvenIfNoLine*/true ); |
| } |
| |
| m_pageMargins.nLeft += sal::static_int_cast<sal_uInt16>(rLRSpace.GetLeft()); |
| m_pageMargins.nRight += sal::static_int_cast<sal_uInt16>(rLRSpace.GetRight()); |
| // if page layout is 'left' then left/right margin need to be exchanged |
| // as it is exported as mirrored layout starting with even page |
| const WW8_SepInfo *pSectionInfo = m_rExport.Sections().CurrentSectionInfo(); |
| if (pSectionInfo->pPageDesc && |
| m_rExport.isMirroredMargin() && |
| ((pSectionInfo->pPageDesc->ReadUseOn() & UseOnPage::All) == UseOnPage::Left)) |
| { |
| sal_uInt16 nLeft = m_pageMargins.nLeft; |
| m_pageMargins.nLeft = m_pageMargins.nRight; |
| m_pageMargins.nRight = nLeft; |
| } |
| sal_uInt16 nGutter = rLRSpace.GetGutterMargin(); |
| |
| AddToAttrList( m_pSectionSpacingAttrList, |
| FSNS( XML_w, XML_left ), OString::number( m_pageMargins.nLeft ), |
| FSNS( XML_w, XML_right ), OString::number( m_pageMargins.nRight ), |
| FSNS( XML_w, XML_gutter ), OString::number( nGutter ) ); |
| } |
| else |
| { |
| // note: this is not possible for SwTextNode but is for EditEngine! |
| SvxLRSpaceItem const* pLRSpace(&rLRSpace); |
| ::std::optional<SvxLRSpaceItem> oLRSpace; |
| assert(dynamic_cast<SwContentNode const*>(GetExport().m_pOutFormatNode) == nullptr); |
| rtl::Reference<FastAttributeList> pLRSpaceAttrList = FastSerializerHelper::createAttrList(); |
| if ((0 != pLRSpace->GetTextLeft()) || (pLRSpace->IsExplicitZeroMarginValLeft())) |
| { |
| pLRSpaceAttrList->add( FSNS(XML_w, (bEcma ? XML_left : XML_start)), OString::number(pLRSpace->GetTextLeft()) ); |
| } |
| if ((0 != pLRSpace->GetRight()) || (pLRSpace->IsExplicitZeroMarginValRight())) |
| { |
| pLRSpaceAttrList->add( FSNS(XML_w, (bEcma ? XML_right : XML_end)), OString::number(pLRSpace->GetRight()) ); |
| } |
| sal_Int32 const nFirstLineAdjustment = pLRSpace->GetTextFirstLineOffset(); |
| if (nFirstLineAdjustment > 0) |
| pLRSpaceAttrList->add( FSNS( XML_w, XML_firstLine ), OString::number( nFirstLineAdjustment ) ); |
| else |
| pLRSpaceAttrList->add( FSNS( XML_w, XML_hanging ), OString::number( - nFirstLineAdjustment ) ); |
| m_pSerializer->singleElementNS( XML_w, XML_ind, pLRSpaceAttrList ); |
| } |
| } |
| |
| void DocxAttributeOutput::FormatULSpace( const SvxULSpaceItem& rULSpace ) |
| { |
| |
| if (m_rExport.SdrExporter().getTextFrameSyntax()) |
| { |
| m_rExport.SdrExporter().getTextFrameStyle().append(";mso-wrap-distance-top:" + OString::number(double(rULSpace.GetUpper()) / 20) + "pt"); |
| m_rExport.SdrExporter().getTextFrameStyle().append(";mso-wrap-distance-bottom:" + OString::number(double(rULSpace.GetLower()) / 20) + "pt"); |
| } |
| else if (m_rExport.SdrExporter().getDMLTextFrameSyntax()) |
| { |
| } |
| else if ( m_rExport.m_bOutFlyFrameAttrs ) |
| { |
| AddToAttrList( m_rExport.SdrExporter().getFlyAttrList(), FSNS( XML_w, XML_vSpace ), |
| OString::number( |
| ( rULSpace.GetLower() + rULSpace.GetUpper() ) / 2 ) ); |
| } |
| else if (m_rExport.m_bOutPageDescs ) |
| { |
| OSL_ENSURE( m_rExport.GetCurItemSet(), "Impossible" ); |
| if ( !m_rExport.GetCurItemSet() ) |
| return; |
| |
| HdFtDistanceGlue aDistances( *m_rExport.GetCurItemSet() ); |
| |
| sal_Int32 nHeader = 0; |
| if ( aDistances.HasHeader() ) |
| nHeader = sal_Int32( aDistances.m_DyaHdrTop ); |
| else if (m_rExport.m_pFirstPageFormat) |
| { |
| HdFtDistanceGlue aFirstPageDistances(m_rExport.m_pFirstPageFormat->GetAttrSet()); |
| if (aFirstPageDistances.HasHeader()) |
| { |
| // The follow page style has no header, but the first page style has. In Word terms, |
| // this means that the header margin of "the" section is coming from the first page |
| // style. |
| nHeader = sal_Int32(aFirstPageDistances.m_DyaHdrTop); |
| } |
| } |
| |
| // Page top |
| m_pageMargins.nTop = aDistances.m_DyaTop; |
| |
| sal_Int32 nFooter = 0; |
| if ( aDistances.HasFooter() ) |
| nFooter = sal_Int32( aDistances.m_DyaHdrBottom ); |
| else if (m_rExport.m_pFirstPageFormat) |
| { |
| HdFtDistanceGlue aFirstPageDistances(m_rExport.m_pFirstPageFormat->GetAttrSet()); |
| if (aFirstPageDistances.HasFooter()) |
| { |
| // The follow page style has no footer, but the first page style has. In Word terms, |
| // this means that the footer margin of "the" section is coming from the first page |
| // style. |
| nFooter = sal_Int32(aFirstPageDistances.m_DyaHdrBottom); |
| } |
| } |
| |
| // Page Bottom |
| m_pageMargins.nBottom = aDistances.m_DyaBottom; |
| |
| AddToAttrList( m_pSectionSpacingAttrList, |
| FSNS( XML_w, XML_header ), OString::number( nHeader ), |
| FSNS( XML_w, XML_top ), OString::number( m_pageMargins.nTop ), |
| FSNS( XML_w, XML_footer ), OString::number( nFooter ), |
| FSNS( XML_w, XML_bottom ), OString::number( m_pageMargins.nBottom ) ); |
| } |
| else |
| { |
| SAL_INFO("sw.ww8", "DocxAttributeOutput::FormatULSpace: setting spacing" << rULSpace.GetUpper() ); |
| // check if before auto spacing was set during import and spacing we get from actual object is same |
| // that we set in import. If yes just write beforeAutoSpacing tag. |
| if (m_bParaBeforeAutoSpacing && m_nParaBeforeSpacing == rULSpace.GetUpper()) |
| { |
| AddToAttrList( m_pParagraphSpacingAttrList, |
| FSNS( XML_w, XML_beforeAutospacing ), "1" ); |
| } |
| else if (m_bParaBeforeAutoSpacing && m_nParaBeforeSpacing == -1) |
| { |
| AddToAttrList( m_pParagraphSpacingAttrList, |
| FSNS( XML_w, XML_beforeAutospacing ), "0", |
| FSNS( XML_w, XML_before ), OString::number( rULSpace.GetUpper() ) ); |
| } |
| else |
| { |
| AddToAttrList( m_pParagraphSpacingAttrList, |
| FSNS( XML_w, XML_before ), OString::number( rULSpace.GetUpper() ) ); |
| } |
| m_bParaBeforeAutoSpacing = false; |
| // check if after auto spacing was set during import and spacing we get from actual object is same |
| // that we set in import. If yes just write afterAutoSpacing tag. |
| if (m_bParaAfterAutoSpacing && m_nParaAfterSpacing == rULSpace.GetLower()) |
| { |
| AddToAttrList( m_pParagraphSpacingAttrList, |
| FSNS( XML_w, XML_afterAutospacing ), "1" ); |
| } |
| else if (m_bParaAfterAutoSpacing && m_nParaAfterSpacing == -1) |
| { |
| AddToAttrList( m_pParagraphSpacingAttrList, |
| FSNS( XML_w, XML_afterAutospacing ), "0", |
| FSNS( XML_w, XML_after ), OString::number( rULSpace.GetLower()) ); |
| } |
| else |
| { |
| AddToAttrList( m_pParagraphSpacingAttrList, |
| FSNS( XML_w, XML_after ), OString::number( rULSpace.GetLower()) ); |
| } |
| m_bParaAfterAutoSpacing = false; |
| |
| if (rULSpace.GetContext()) |
| m_pSerializer->singleElementNS(XML_w, XML_contextualSpacing); |
| else |
| { |
| // Write out Contextual Spacing = false if it would have inherited a true. |
| const SvxULSpaceItem* pInherited = nullptr; |
| if (auto pNd = dynamic_cast<const SwContentNode*>(m_rExport.m_pOutFormatNode)) //paragraph |
| pInherited = &static_cast<SwTextFormatColl&>(pNd->GetAnyFormatColl()).GetAttrSet().GetULSpace(); |
| else if (m_rExport.m_bStyDef && m_rExport.m_pCurrentStyle && m_rExport.m_pCurrentStyle->DerivedFrom()) //style |
| pInherited = &m_rExport.m_pCurrentStyle->DerivedFrom()->GetULSpace(); |
| |
| if (pInherited && pInherited->GetContext()) |
| m_pSerializer->singleElementNS(XML_w, XML_contextualSpacing, FSNS(XML_w, XML_val), "false"); |
| } |
| } |
| } |
| |
| namespace docx { |
| |
| rtl::Reference<FastAttributeList> SurroundToVMLWrap(SwFormatSurround const& rSurround) |
| { |
| std::string_view sType; |
| std::string_view sSide; |
| switch (rSurround.GetSurround()) |
| { |
| case css::text::WrapTextMode_NONE: |
| sType = "topAndBottom"; |
| break; |
| case css::text::WrapTextMode_PARALLEL: |
| sType = "square"; |
| break; |
| case css::text::WrapTextMode_DYNAMIC: |
| sType = "square"; |
| sSide = "largest"; |
| break; |
| case css::text::WrapTextMode_LEFT: |
| sType = "square"; |
| sSide = "left"; |
| break; |
| case css::text::WrapTextMode_RIGHT: |
| sType = "square"; |
| sSide = "right"; |
| break; |
| case css::text::WrapTextMode_THROUGH: |
| /* empty type and side means through */ |
| default: |
| sType = "none"; |
| break; |
| } |
| rtl::Reference<FastAttributeList> pAttrList; |
| if (!sType.empty()) |
| DocxAttributeOutput::AddToAttrList(pAttrList, XML_type, sType); |
| if (!sSide.empty()) |
| DocxAttributeOutput::AddToAttrList(pAttrList, XML_side, sSide); |
| return pAttrList; |
| } |
| |
| } // namespace docx |
| |
| void DocxAttributeOutput::FormatSurround( const SwFormatSurround& rSurround ) |
| { |
| if (m_rExport.SdrExporter().getTextFrameSyntax()) |
| { |
| rtl::Reference<FastAttributeList> pAttrList(docx::SurroundToVMLWrap(rSurround)); |
| if (pAttrList) |
| { |
| m_rExport.SdrExporter().setFlyWrapAttrList(pAttrList); |
| } |
| } |
| else if (m_rExport.SdrExporter().getDMLTextFrameSyntax()) |
| { |
| } |
| else if ( m_rExport.m_bOutFlyFrameAttrs ) |
| { |
| std::string_view sWrap; |
| switch ( rSurround.GetSurround( ) ) |
| { |
| case css::text::WrapTextMode_NONE: |
| sWrap = "none"; |
| break; |
| case css::text::WrapTextMode_THROUGH: |
| sWrap = "through"; |
| break; |
| case css::text::WrapTextMode_DYNAMIC: |
| case css::text::WrapTextMode_PARALLEL: |
| case css::text::WrapTextMode_LEFT: |
| case css::text::WrapTextMode_RIGHT: |
| default: |
| sWrap = "around"; |
| } |
| |
| AddToAttrList( m_rExport.SdrExporter().getFlyAttrList(), FSNS( XML_w, XML_wrap ), sWrap ); |
| } |
| } |
| |
| void DocxAttributeOutput::FormatVertOrientation( const SwFormatVertOrient& rFlyVert ) |
| { |
| OString sAlign = convertToOOXMLVertOrient( rFlyVert.GetVertOrient() ); |
| OString sVAnchor = convertToOOXMLVertOrientRel( rFlyVert.GetRelationOrient() ); |
| |
| if (m_rExport.SdrExporter().getTextFrameSyntax()) |
| { |
| m_rExport.SdrExporter().getTextFrameStyle().append(";margin-top:" + OString::number(double(rFlyVert.GetPos()) / 20) + "pt"); |
| if ( !sAlign.isEmpty() ) |
| m_rExport.SdrExporter().getTextFrameStyle().append(";mso-position-vertical:" + sAlign); |
| m_rExport.SdrExporter().getTextFrameStyle().append(";mso-position-vertical-relative:" + sVAnchor); |
| } |
| else if (m_rExport.SdrExporter().getDMLTextFrameSyntax()) |
| { |
| } |
| else if ( m_rExport.m_bOutFlyFrameAttrs ) |
| { |
| if ( !sAlign.isEmpty() ) |
| AddToAttrList( m_rExport.SdrExporter().getFlyAttrList(), FSNS( XML_w, XML_yAlign ), sAlign ); |
| else |
| AddToAttrList( m_rExport.SdrExporter().getFlyAttrList(), FSNS( XML_w, XML_y ), |
| OString::number( rFlyVert.GetPos() ) ); |
| AddToAttrList( m_rExport.SdrExporter().getFlyAttrList(), FSNS( XML_w, XML_vAnchor ), sVAnchor ); |
| } |
| } |
| |
| void DocxAttributeOutput::FormatHorizOrientation( const SwFormatHoriOrient& rFlyHori ) |
| { |
| OString sAlign = convertToOOXMLHoriOrient( rFlyHori.GetHoriOrient(), rFlyHori.IsPosToggle() ); |
| OString sHAnchor = convertToOOXMLHoriOrientRel( rFlyHori.GetRelationOrient() ); |
| |
| if (m_rExport.SdrExporter().getTextFrameSyntax()) |
| { |
| m_rExport.SdrExporter().getTextFrameStyle().append(";margin-left:" + OString::number(double(rFlyHori.GetPos()) / 20) + "pt"); |
| if ( !sAlign.isEmpty() ) |
| m_rExport.SdrExporter().getTextFrameStyle().append(";mso-position-horizontal:" + sAlign); |
| m_rExport.SdrExporter().getTextFrameStyle().append(";mso-position-horizontal-relative:" + sHAnchor); |
| } |
| else if (m_rExport.SdrExporter().getDMLTextFrameSyntax()) |
| { |
| } |
| else if ( m_rExport.m_bOutFlyFrameAttrs ) |
| { |
| if ( !sAlign.isEmpty() ) |
| AddToAttrList( m_rExport.SdrExporter().getFlyAttrList(), FSNS( XML_w, XML_xAlign ), sAlign ); |
| else |
| AddToAttrList( m_rExport.SdrExporter().getFlyAttrList(), FSNS( XML_w, XML_x ), |
| OString::number( rFlyHori.GetPos() ) ); |
| AddToAttrList( m_rExport.SdrExporter().getFlyAttrList(), FSNS( XML_w, XML_hAnchor ), sHAnchor ); |
| } |
| } |
| |
| void DocxAttributeOutput::FormatAnchor( const SwFormatAnchor& ) |
| { |
| // Fly frames: anchors here aren't matching the anchors in docx |
| } |
| |
| static std::optional<sal_Int32> lcl_getDmlAlpha(const SvxBrushItem& rBrush) |
| { |
| std::optional<sal_Int32> oRet; |
| sal_Int32 nTransparency = 255 - rBrush.GetColor().GetAlpha(); |
| if (nTransparency) |
| { |
| // Convert transparency to percent |
| sal_Int8 nTransparencyPercent = SvxBrushItem::TransparencyToPercent(nTransparency); |
| |
| // Calculate alpha value |
| // Consider oox/source/drawingml/color.cxx : getTransparency() function. |
| sal_Int32 nAlpha = ::oox::drawingml::MAX_PERCENT - ( ::oox::drawingml::PER_PERCENT * nTransparencyPercent ); |
| oRet = nAlpha; |
| } |
| return oRet; |
| } |
| |
| void DocxAttributeOutput::FormatBackground( const SvxBrushItem& rBrush ) |
| { |
| const Color aColor = rBrush.GetColor(); |
| model::ComplexColor const& rComplexColor = rBrush.getComplexColor(); |
| OString sColor = msfilter::util::ConvertColor( aColor.GetRGBColor() ); |
| std::optional<sal_Int32> oAlpha = lcl_getDmlAlpha(rBrush); |
| if (m_rExport.SdrExporter().getTextFrameSyntax()) |
| { |
| // Handle 'Opacity' |
| if (oAlpha) |
| { |
| // Calculate opacity value |
| // Consider oox/source/vml/vmlformatting.cxx : decodeColor() function. |
| double fOpacity = static_cast<double>(*oAlpha) * 65535 / ::oox::drawingml::MAX_PERCENT; |
| |
| AddToAttrList( m_rExport.SdrExporter().getFlyFillAttrList(), XML_opacity, OString::number(fOpacity) + "f" ); |
| } |
| |
| AddToAttrList(m_rExport.SdrExporter().getFlyAttrList(), XML_fillcolor, "#" + sColor ); |
| lclAddThemeFillColorAttributes(m_rExport.SdrExporter().getFlyAttrList(), rComplexColor); |
| } |
| else if (m_rExport.SdrExporter().getDMLTextFrameSyntax()) |
| { |
| bool bImageBackground = false; |
| const SfxPoolItem* pItem = GetExport().HasItem(XATTR_FILLSTYLE); |
| if (pItem) |
| { |
| const XFillStyleItem* pFillStyle = static_cast<const XFillStyleItem*>(pItem); |
| if(pFillStyle->GetValue() == drawing::FillStyle_BITMAP) |
| { |
| bImageBackground = true; |
| } |
| } |
| if (!bImageBackground) |
| { |
| m_pSerializer->startElementNS(XML_a, XML_solidFill); |
| m_pSerializer->startElementNS(XML_a, XML_srgbClr, XML_val, sColor); |
| if (oAlpha) |
| m_pSerializer->singleElementNS(XML_a, XML_alpha, |
| XML_val, OString::number(*oAlpha)); |
| m_pSerializer->endElementNS(XML_a, XML_srgbClr); |
| m_pSerializer->endElementNS(XML_a, XML_solidFill); |
| } |
| } |
| else if ( !m_rExport.m_bOutPageDescs ) |
| { |
| // compare fill color with the original fill color |
| OString sOriginalFill = OUStringToOString( |
| m_sOriginalBackgroundColor, RTL_TEXTENCODING_UTF8 ); |
| |
| if ( aColor == COL_AUTO ) |
| sColor = "auto"_ostr; |
| |
| if( !m_pBackgroundAttrList.is() ) |
| { |
| m_pBackgroundAttrList = FastSerializerHelper::createAttrList(); |
| m_pBackgroundAttrList->add(FSNS(XML_w, XML_fill), sColor); |
| m_pBackgroundAttrList->add( FSNS( XML_w, XML_val ), "clear" ); |
| } |
| else if ( sOriginalFill != sColor ) |
| { |
| // fill was modified during edition, theme fill attribute must be dropped |
| m_pBackgroundAttrList = FastSerializerHelper::createAttrList(); |
| m_pBackgroundAttrList->add(FSNS(XML_w, XML_fill), sColor); |
| m_pBackgroundAttrList->add( FSNS( XML_w, XML_val ), "clear" ); |
| } |
| m_sOriginalBackgroundColor.clear(); |
| } |
| } |
| |
| void DocxAttributeOutput::FormatFillStyle( const XFillStyleItem& rFillStyle ) |
| { |
| if (!m_bIgnoreNextFill) |
| m_oFillStyle = rFillStyle.GetValue(); |
| else |
| { |
| m_bIgnoreNextFill = false; |
| // ITEM: Still need to signal that ::FormatFillStyle was called so that |
| // ::FormatFillGradient does not assert but do nothing |
| m_oFillStyle = drawing::FillStyle_NONE; |
| } |
| |
| // Don't round-trip grabbag OriginalBackground if the background has been cleared. |
| if ( m_pBackgroundAttrList.is() && m_sOriginalBackgroundColor != "auto" && rFillStyle.GetValue() == drawing::FillStyle_NONE ) |
| m_pBackgroundAttrList.clear(); |
| } |
| |
| void DocxAttributeOutput::FormatFillGradient( const XFillGradientItem& rFillGradient ) |
| { |
| assert(m_oFillStyle && "ITEM: FormatFillStyle *has* to be called before FormatFillGradient(!)"); |
| if (m_oFillStyle && *m_oFillStyle == drawing::FillStyle_GRADIENT && !m_rExport.SdrExporter().getDMLTextFrameSyntax()) |
| { |
| const basegfx::BGradient& rGradient = rFillGradient.GetGradientValue(); |
| OString sStartColor = msfilter::util::ConvertColor(Color(rGradient.GetColorStops().front().getStopColor())); |
| OString sEndColor = msfilter::util::ConvertColor(Color(rGradient.GetColorStops().back().getStopColor())); |
| |
| const sal_Int32 nAngle = toDegrees(rGradient.GetAngle()); |
| if (nAngle != 0) |
| AddToAttrList( m_rExport.SdrExporter().getFlyFillAttrList(), |
| XML_angle, OString::number(nAngle)); |
| |
| // LO does linear gradients top to bottom, while MSO does bottom to top. |
| // LO does axial gradients inner to outer, while MSO does outer to inner. |
| // Conclusion: swap start and end colors. |
| const OString sColor1 = sEndColor; // LO end color is MSO start color |
| OString sColor2 = sStartColor; // LO start color is MSO end color |
| |
| switch (rGradient.GetGradientStyle()) |
| { |
| case css::awt::GradientStyle_AXIAL: |
| case css::awt::GradientStyle_LINEAR: |
| { |
| bool bIsSymmetrical = rGradient.GetGradientStyle() == css::awt::GradientStyle_AXIAL; |
| if (!bIsSymmetrical) |
| { |
| const basegfx::BColorStops& rColorStops = rGradient.GetColorStops(); |
| if (rColorStops.size() > 2 && rColorStops.isSymmetrical()) |
| { |
| for (auto& rStop : rColorStops) |
| { |
| if (basegfx::fTools::less(rStop.getStopOffset(), 0.5)) |
| continue; |
| if (basegfx::fTools::more(rStop.getStopOffset(), 0.5)) |
| break; |
| |
| // from MSO export perspective, the inner color is the end color |
| sColor2 = msfilter::util::ConvertColor(Color(rStop.getStopColor())); |
| bIsSymmetrical = true; |
| } |
| } |
| } |
| |
| if (bIsSymmetrical) |
| AddToAttrList( m_rExport.SdrExporter().getFlyFillAttrList(), XML_focus, "50%" ); |
| |
| AddToAttrList(m_rExport.SdrExporter().getFlyFillAttrList(), XML_type, "gradient"); |
| break; |
| } |
| case css::awt::GradientStyle_RADIAL: |
| case css::awt::GradientStyle_ELLIPTICAL: |
| case css::awt::GradientStyle_SQUARE: |
| case css::awt::GradientStyle_RECT: |
| AddToAttrList(m_rExport.SdrExporter().getFlyFillAttrList(), XML_type, |
| "gradientRadial"); |
| break; |
| default: |
| break; |
| } |
| |
| AddToAttrList( m_rExport.SdrExporter().getFlyAttrList(), XML_fillcolor, "#" + sColor1 ); |
| AddToAttrList( m_rExport.SdrExporter().getFlyFillAttrList(), XML_color2, "#" + sColor2 ); |
| } |
| else if (m_oFillStyle && *m_oFillStyle == drawing::FillStyle_GRADIENT && m_rExport.SdrExporter().getDMLTextFrameSyntax()) |
| { |
| SwFrameFormat & rFormat( |
| const_cast<SwFrameFormat&>(m_rExport.m_pParentFrame->GetFrameFormat())); |
| rtl::Reference<SwXTextFrame> const xPropertySet = |
| SwXTextFrame::CreateXTextFrame(*rFormat.GetDoc(), &rFormat); |
| m_rDrawingML.SetFS(m_pSerializer); |
| m_rDrawingML.WriteGradientFill(uno::Reference<beans::XPropertySet>(static_cast<SwXFrame*>(xPropertySet.get()))); |
| } |
| m_oFillStyle.reset(); |
| } |
| |
| void DocxAttributeOutput::FormatBox( const SvxBoxItem& rBox ) |
| { |
| if (m_rExport.SdrExporter().getDMLTextFrameSyntax()) |
| { |
| // ugh, exporting fill here is quite some hack... this OutputItemSet abstraction is quite leaky |
| // <a:gradFill> should be before <a:ln>. |
| const SfxPoolItem* pItem = GetExport().HasItem(XATTR_FILLSTYLE); |
| if (pItem) |
| { |
| const XFillStyleItem* pFillStyle = static_cast<const XFillStyleItem*>(pItem); |
| FormatFillStyle(*pFillStyle); |
| if (m_oFillStyle && *m_oFillStyle == drawing::FillStyle_BITMAP) |
| { |
| const SdrObject* pSdrObj = m_rExport.m_pParentFrame->GetFrameFormat().FindRealSdrObject(); |
| if (pSdrObj) |
| { |
| uno::Reference< drawing::XShape > xShape( const_cast<SdrObject*>(pSdrObj)->getUnoShape(), uno::UNO_QUERY ); |
| uno::Reference< beans::XPropertySet > xPropertySet( xShape, uno::UNO_QUERY ); |
| m_rDrawingML.SetFS(m_pSerializer); |
| m_rDrawingML.WriteBlipFill(xPropertySet, u"BackGraphic"_ustr); |
| } |
| } |
| } |
| |
| pItem = GetExport().HasItem(XATTR_FILLGRADIENT); |
| if (pItem) |
| { |
| const XFillGradientItem* pFillGradient = static_cast<const XFillGradientItem*>(pItem); |
| FormatFillGradient(*pFillGradient); |
| } |
| m_bIgnoreNextFill = true; |
| } |
| if (m_rExport.SdrExporter().getTextFrameSyntax() || m_rExport.SdrExporter().getDMLTextFrameSyntax()) |
| { |
| const SvxBorderLine* pLeft = rBox.GetLeft( ); |
| const SvxBorderLine* pTop = rBox.GetTop( ); |
| const SvxBorderLine* pRight = rBox.GetRight( ); |
| const SvxBorderLine* pBottom = rBox.GetBottom( ); |
| |
| if (pLeft && pRight && pTop && pBottom && |
| *pLeft == *pRight && *pLeft == *pTop && *pLeft == *pBottom) |
| { |
| // Check border style |
| SvxBorderLineStyle eBorderStyle = pTop->GetBorderLineStyle(); |
| if (eBorderStyle == SvxBorderLineStyle::NONE) |
| { |
| if (m_rExport.SdrExporter().getTextFrameSyntax()) |
| { |
| AddToAttrList( m_rExport.SdrExporter().getFlyAttrList(), |
| XML_stroked, "f", XML_strokeweight, "0pt" ); |
| } |
| } |
| else |
| { |
| OString sColor(msfilter::util::ConvertColor(pTop->GetColor())); |
| double const fConverted(editeng::ConvertBorderWidthToWord(pTop->GetBorderLineStyle(), pTop->GetWidth())); |
| |
| if (m_rExport.SdrExporter().getTextFrameSyntax()) |
| { |
| sal_Int32 nWidth = sal_Int32(fConverted / 20); |
| AddToAttrList( m_rExport.SdrExporter().getFlyAttrList(), |
| XML_strokecolor, "#" + sColor, |
| XML_strokeweight, OString::number(nWidth) + "pt" ); |
| if( SvxBorderLineStyle::DASHED == pTop->GetBorderLineStyle() ) // Line Style is Dash type |
| AddToAttrList( m_rExport.SdrExporter().getDashLineStyle(), |
| XML_dashstyle, "dash" ); |
| } |
| else |
| m_rExport.SdrExporter().writeBoxItemLine(rBox); |
| } |
| } |
| |
| if (m_rExport.SdrExporter().getDMLTextFrameSyntax()) |
| { |
| m_rExport.SdrExporter().getBodyPrAttrList()->add(XML_lIns, OString::number(TwipsToEMU(rBox.GetDistance(SvxBoxItemLine::LEFT)))); |
| m_rExport.SdrExporter().getBodyPrAttrList()->add(XML_tIns, OString::number(TwipsToEMU(rBox.GetDistance(SvxBoxItemLine::TOP)))); |
| m_rExport.SdrExporter().getBodyPrAttrList()->add(XML_rIns, OString::number(TwipsToEMU(rBox.GetDistance(SvxBoxItemLine::RIGHT)))); |
| m_rExport.SdrExporter().getBodyPrAttrList()->add(XML_bIns, OString::number(TwipsToEMU(rBox.GetDistance(SvxBoxItemLine::BOTTOM)))); |
| return; |
| } |
| |
| // v:textbox's inset attribute: inner margin values for textbox text - write only non-default values |
| double fDistanceLeftTwips = double(rBox.GetDistance(SvxBoxItemLine::LEFT)); |
| double fDistanceTopTwips = double(rBox.GetDistance(SvxBoxItemLine::TOP)); |
| double fDistanceRightTwips = double(rBox.GetDistance(SvxBoxItemLine::RIGHT)); |
| double fDistanceBottomTwips = double(rBox.GetDistance(SvxBoxItemLine::BOTTOM)); |
| |
| // Convert 'TWIPS' to 'INCH' (because in Word the default values are in Inches) |
| double fDistanceLeftInch = o3tl::convert(fDistanceLeftTwips, o3tl::Length::twip, o3tl::Length::in); |
| double fDistanceTopInch = o3tl::convert(fDistanceTopTwips, o3tl::Length::twip, o3tl::Length::in); |
| double fDistanceRightInch = o3tl::convert(fDistanceRightTwips, o3tl::Length::twip, o3tl::Length::in); |
| double fDistanceBottomInch = o3tl::convert(fDistanceBottomTwips, o3tl::Length::twip, o3tl::Length::in); |
| |
| // This code will write ONLY the non-default values. The values are in 'left','top','right','bottom' order. |
| // so 'bottom' is checked if it is default and if it is non-default - all the values will be written |
| // otherwise - 'right' is checked if it is default and if it is non-default - all the values except for 'bottom' will be written |
| // and so on. |
| OStringBuffer aInset; |
| if(!aInset.isEmpty() || fDistanceBottomInch != 0.05) |
| aInset.insert(0, Concat2View("," + OString::number(fDistanceBottomInch) + "in")); |
| |
| if(!aInset.isEmpty() || fDistanceRightInch != 0.1) |
| aInset.insert(0, Concat2View("," + OString::number(fDistanceRightInch) + "in")); |
| |
| if(!aInset.isEmpty() || fDistanceTopInch != 0.05) |
| aInset.insert(0, Concat2View("," + OString::number(fDistanceTopInch) + "in")); |
| |
| if(!aInset.isEmpty() || fDistanceLeftInch != 0.1) |
| aInset.insert(0, Concat2View(OString::number(fDistanceLeftInch) + "in")); |
| |
| if (!aInset.isEmpty()) |
| m_rExport.SdrExporter().getTextboxAttrList()->add(XML_inset, aInset); |
| |
| return; |
| } |
| |
| OutputBorderOptions aOutputBorderOptions = lcl_getBoxBorderOptions(); |
| // Check if there is a shadow item |
| const SfxPoolItem* pItem = GetExport().HasItem( RES_SHADOW ); |
| if ( pItem ) |
| { |
| const SvxShadowItem* pShadowItem = static_cast<const SvxShadowItem*>(pItem); |
| aOutputBorderOptions.aShadowLocation = pShadowItem->GetLocation(); |
| } |
| |
| if ( m_bOpenedSectPr && !GetWritingHeaderFooter()) |
| return; |
| |
| // Not inside a section |
| |
| // Open the paragraph's borders tag |
| m_pSerializer->startElementNS(XML_w, XML_pBdr); |
| |
| std::map<SvxBoxItemLine, css::table::BorderLine2> aStyleBorders; |
| const SvxBoxItem* pInherited = nullptr; |
| if ( GetExport().m_pStyAttr ) |
| pInherited = GetExport().m_pStyAttr->GetItem<SvxBoxItem>(RES_BOX); |
| else if ( GetExport().m_pCurrentStyle && GetExport().m_pCurrentStyle->DerivedFrom() ) |
| pInherited = GetExport().m_pCurrentStyle->DerivedFrom()->GetAttrSet().GetItem<SvxBoxItem>(RES_BOX); |
| |
| if ( pInherited ) |
| { |
| aStyleBorders[ SvxBoxItemLine::TOP ] = SvxBoxItem::SvxLineToLine(pInherited->GetTop(), /*bConvert=*/false); |
| aStyleBorders[ SvxBoxItemLine::BOTTOM ] = SvxBoxItem::SvxLineToLine(pInherited->GetBottom(), false); |
| aStyleBorders[ SvxBoxItemLine::LEFT ] = SvxBoxItem::SvxLineToLine(pInherited->GetLeft(), false); |
| aStyleBorders[ SvxBoxItemLine::RIGHT ] = SvxBoxItem::SvxLineToLine(pInherited->GetRight(), false); |
| } |
| bool bUseFrame = m_aFramePr.UseFrameBorders(!m_xTableWrt ? -1 : m_tableReference.m_nTableDepth); |
| impl_borders(m_pSerializer, rBox, aOutputBorderOptions, aStyleBorders, |
| bUseFrame ? m_aFramePr.Frame() : nullptr); |
| |
| // Close the paragraph's borders tag |
| m_pSerializer->endElementNS( XML_w, XML_pBdr ); |
| |
| m_aFramePr.SetUseFrameBorders(false); |
| } |
| |
| void DocxAttributeOutput::FormatColumns_Impl( sal_uInt16 nCols, const SwFormatCol& rCol, bool bEven, SwTwips nPageSize ) |
| { |
| // Get the columns attributes |
| rtl::Reference<FastAttributeList> pColsAttrList = FastSerializerHelper::createAttrList(); |
| |
| pColsAttrList->add( FSNS( XML_w, XML_num ), OString::number( nCols ) ); |
| |
| std::string_view pEquals = "false"; |
| if ( bEven ) |
| { |
| sal_uInt16 nWidth = rCol.GetGutterWidth( true ); |
| pColsAttrList->add( FSNS( XML_w, XML_space ), OString::number( nWidth ) ); |
| |
| pEquals = "true"; |
| } |
| |
| pColsAttrList->add( FSNS( XML_w, XML_equalWidth ), pEquals ); |
| |
| bool bHasSep = (COLADJ_NONE != rCol.GetLineAdj()); |
| |
| pColsAttrList->add( FSNS( XML_w, XML_sep ), OString::boolean( bHasSep ) ); |
| |
| // Write the element |
| m_pSerializer->startElementNS( XML_w, XML_cols, pColsAttrList ); |
| |
| // Write the columns width if non-equals |
| const SwColumns & rColumns = rCol.GetColumns( ); |
| if ( !bEven ) |
| { |
| for ( sal_uInt16 n = 0; n < nCols; ++n ) |
| { |
| rtl::Reference<FastAttributeList> pColAttrList = FastSerializerHelper::createAttrList(); |
| sal_uInt16 nWidth = rCol.CalcPrtColWidth( n, o3tl::narrowing<sal_uInt16>(nPageSize) ); |
| pColAttrList->add( FSNS( XML_w, XML_w ), OString::number( nWidth ) ); |
| |
| if ( n + 1 != nCols ) |
| { |
| sal_uInt16 nSpacing = rColumns[n].GetRight( ) + rColumns[n + 1].GetLeft( ); |
| pColAttrList->add( FSNS( XML_w, XML_space ), OString::number( nSpacing ) ); |
| } |
| |
| m_pSerializer->singleElementNS( XML_w, XML_col, pColAttrList ); |
| } |
| } |
| |
| m_pSerializer->endElementNS( XML_w, XML_cols ); |
| } |
| |
| void DocxAttributeOutput::FormatKeep( const SvxFormatKeepItem& rItem ) |
| { |
| m_pSerializer->singleElementNS( XML_w, XML_keepNext, |
| FSNS( XML_w, XML_val ), OString::boolean( rItem.GetValue() ) ); |
| } |
| |
| void DocxAttributeOutput::FormatTextGrid( const SwTextGridItem& rGrid ) |
| { |
| rtl::Reference<FastAttributeList> pGridAttrList = FastSerializerHelper::createAttrList(); |
| |
| std::string_view sGridType; |
| switch ( rGrid.GetGridType( ) ) |
| { |
| default: |
| case GRID_NONE: |
| sGridType = "default"; |
| break; |
| case GRID_LINES_ONLY: |
| sGridType = "lines"; |
| break; |
| case GRID_LINES_CHARS: |
| if ( rGrid.IsSnapToChars( ) ) |
| sGridType = "snapToChars"; |
| else |
| sGridType = "linesAndChars"; |
| break; |
| } |
| pGridAttrList->add(FSNS(XML_w, XML_type), sGridType); |
| |
| sal_uInt16 nHeight = rGrid.GetBaseHeight() + rGrid.GetRubyHeight(); |
| pGridAttrList->add( FSNS( XML_w, XML_linePitch ), |
| OString::number( nHeight ) ); |
| |
| pGridAttrList->add( FSNS( XML_w, XML_charSpace ), |
| OString::number( GridCharacterPitch( rGrid ) ) ); |
| |
| m_pSerializer->singleElementNS( XML_w, XML_docGrid, pGridAttrList ); |
| } |
| |
| void DocxAttributeOutput::FormatLineNumbering( const SwFormatLineNumber& rNumbering ) |
| { |
| if ( !rNumbering.IsCount( ) ) |
| m_pSerializer->singleElementNS(XML_w, XML_suppressLineNumbers); |
| else |
| m_pSerializer->singleElementNS(XML_w, XML_suppressLineNumbers, FSNS(XML_w, XML_val), "0"); |
| } |
| |
| void DocxAttributeOutput::FormatFrameDirection( const SvxFrameDirectionItem& rDirection ) |
| { |
| OString sTextFlow; |
| bool bBiDi = false; |
| SvxFrameDirection nDir = rDirection.GetValue(); |
| |
| if ( nDir == SvxFrameDirection::Environment ) |
| nDir = GetExport( ).GetDefaultFrameDirection( ); |
| |
| switch ( nDir ) |
| { |
| default: |
| case SvxFrameDirection::Horizontal_LR_TB: |
| sTextFlow = "lrTb"_ostr; |
| break; |
| case SvxFrameDirection::Horizontal_RL_TB: |
| sTextFlow = "lrTb"_ostr; |
| bBiDi = true; |
| break; |
| case SvxFrameDirection::Vertical_LR_TB: // ~ vert="mongolianVert" |
| sTextFlow = "tbLrV"_ostr; |
| break; |
| case SvxFrameDirection::Vertical_RL_TB: // ~ vert="eaVert" |
| sTextFlow = "tbRl"_ostr; |
| break; |
| case SvxFrameDirection::Vertical_LR_BT: // ~ vert="vert270" |
| sTextFlow = "btLr"_ostr; |
| break; |
| case SvxFrameDirection::Vertical_RL_TB90: // ~ vert="vert" |
| sTextFlow = "tbRlV"_ostr; |
| break; |
| } |
| |
| if ( m_rExport.m_bOutPageDescs ) |
| { |
| m_pSerializer->singleElementNS(XML_w, XML_textDirection, FSNS(XML_w, XML_val), sTextFlow); |
| if ( bBiDi ) |
| m_pSerializer->singleElementNS(XML_w, XML_bidi); |
| } |
| else if ( !m_rExport.m_bOutFlyFrameAttrs ) |
| { |
| if ( bBiDi ) |
| m_pSerializer->singleElementNS(XML_w, XML_bidi, FSNS(XML_w, XML_val), "1"); |
| else |
| m_pSerializer->singleElementNS(XML_w, XML_bidi, FSNS(XML_w, XML_val), "0"); |
| m_aFramePr.SetUseFrameTextDirection(false); |
| } |
| } |
| |
| void DocxAttributeOutput::ParaGrabBag(const SfxGrabBagItem& rItem) |
| { |
| const std::map<OUString, css::uno::Any>& rMap = rItem.GetGrabBag(); |
| for ( const auto & rGrabBagElement : rMap ) |
| { |
| if (rGrabBagElement.first == "MirrorIndents") |
| m_pSerializer->singleElementNS(XML_w, XML_mirrorIndents); |
| else if (rGrabBagElement.first == "ParaTopMarginBeforeAutoSpacing") |
| { |
| m_bParaBeforeAutoSpacing = true; |
| // get fixed value which was set during import |
| rGrabBagElement.second >>= m_nParaBeforeSpacing; |
| m_nParaBeforeSpacing = o3tl::toTwips(m_nParaBeforeSpacing, o3tl::Length::mm100); |
| SAL_INFO("sw.ww8", "DocxAttributeOutput::ParaGrabBag: property =" << rGrabBagElement.first << " : m_nParaBeforeSpacing= " << m_nParaBeforeSpacing); |
| } |
| else if (rGrabBagElement.first == "ParaBottomMarginAfterAutoSpacing") |
| { |
| m_bParaAfterAutoSpacing = true; |
| // get fixed value which was set during import |
| rGrabBagElement.second >>= m_nParaAfterSpacing; |
| m_nParaAfterSpacing = o3tl::toTwips(m_nParaAfterSpacing, o3tl::Length::mm100); |
| SAL_INFO("sw.ww8", "DocxAttributeOutput::ParaGrabBag: property =" << rGrabBagElement.first << " : m_nParaBeforeSpacing= " << m_nParaAfterSpacing); |
| } |
| else if (rGrabBagElement.first == "CharThemeFill") |
| { |
| uno::Sequence<beans::PropertyValue> aGrabBagSeq; |
| rGrabBagElement.second >>= aGrabBagSeq; |
| |
| for (const auto& rProp : aGrabBagSeq) |
| { |
| OUString sVal = rProp.Value.get<OUString>(); |
| |
| if (sVal.isEmpty()) |
| continue; |
| |
| if (rProp.Name == "val") |
| AddToAttrList(m_pBackgroundAttrList, FSNS(XML_w, XML_val), sVal); |
| else if (rProp.Name == "color") |
| AddToAttrList(m_pBackgroundAttrList, FSNS(XML_w, XML_color), sVal); |
| else if (rProp.Name == "themeColor") |
| AddToAttrList(m_pBackgroundAttrList, FSNS(XML_w, XML_themeColor), sVal); |
| else if (rProp.Name == "themeTint") |
| AddToAttrList(m_pBackgroundAttrList, FSNS(XML_w, XML_themeTint), sVal); |
| else if (rProp.Name == "themeShade") |
| AddToAttrList(m_pBackgroundAttrList, FSNS(XML_w, XML_themeShade), sVal); |
| else if (rProp.Name == "fill") |
| AddToAttrList(m_pBackgroundAttrList, FSNS(XML_w, XML_fill), sVal); |
| else if (rProp.Name == "themeFill") |
| AddToAttrList(m_pBackgroundAttrList, FSNS(XML_w, XML_themeFill), sVal); |
| else if (rProp.Name == "themeFillTint") |
| AddToAttrList(m_pBackgroundAttrList, FSNS(XML_w, XML_themeFillTint), sVal); |
| else if (rProp.Name == "themeFillShade") |
| AddToAttrList(m_pBackgroundAttrList, FSNS(XML_w, XML_themeFillShade), sVal); |
| else if (rProp.Name == "originalColor") |
| rProp.Value >>= m_sOriginalBackgroundColor; |
| } |
| } |
| else if (rGrabBagElement.first == "SdtPr") |
| { |
| const uno::Sequence<beans::PropertyValue> aGrabBagSdt = |
| rGrabBagElement.second.get< uno::Sequence<beans::PropertyValue> >(); |
| m_aParagraphSdt.GetSdtParamsFromGrabBag(aGrabBagSdt); |
| m_aStartedParagraphSdtPrAlias = m_aParagraphSdt.m_aAlias; |
| } |
| else if (rGrabBagElement.first == "ParaCnfStyle") |
| { |
| uno::Sequence<beans::PropertyValue> aAttributes = rGrabBagElement.second.get< uno::Sequence<beans::PropertyValue> >(); |
| m_pTableStyleExport->CnfStyle(aAttributes); |
| } |
| else if (rGrabBagElement.first == "ParaSdtEndBefore") |
| { |
| // Handled already in StartParagraph(). |
| } |
| else |
| SAL_WARN("sw.ww8", "DocxAttributeOutput::ParaGrabBag: unhandled grab bag property " << rGrabBagElement.first ); |
| } |
| } |
| |
| void DocxAttributeOutput::CharGrabBag( const SfxGrabBagItem& rItem ) |
| { |
| if (m_bPreventDoubleFieldsHandling) |
| return; |
| |
| const std::map< OUString, css::uno::Any >& rMap = rItem.GetGrabBag(); |
| |
| // get original values of theme-derived properties to check if they have changed during the edition |
| bool bWriteCSTheme = true; |
| bool bWriteAsciiTheme = true; |
| bool bWriteEastAsiaTheme = true; |
| OUString sOriginalValue; |
| for ( const auto & rGrabBagElement : rMap ) |
| { |
| if ( m_pFontsAttrList.is() && rGrabBagElement.first == "CharThemeFontNameCs" ) |
| { |
| if ( rGrabBagElement.second >>= sOriginalValue ) |
| bWriteCSTheme = |
| ( m_pFontsAttrList->getOptionalValue( FSNS( XML_w, XML_cs ) ) == sOriginalValue ); |
| } |
| else if ( m_pFontsAttrList.is() && rGrabBagElement.first == "CharThemeFontNameAscii" ) |
| { |
| if ( rGrabBagElement.second >>= sOriginalValue ) |
| bWriteAsciiTheme = |
| ( m_pFontsAttrList->getOptionalValue( FSNS( XML_w, XML_ascii ) ) == sOriginalValue ); |
| } |
| else if ( m_pFontsAttrList.is() && rGrabBagElement.first == "CharThemeFontNameEastAsia" ) |
| { |
| if ( rGrabBagElement.second >>= sOriginalValue ) |
| bWriteEastAsiaTheme = |
| ( m_pFontsAttrList->getOptionalValue( FSNS( XML_w, XML_eastAsia ) ) == sOriginalValue ); |
| } |
| } |
| |
| // save theme attributes back to the run properties |
| OUString str; |
| for ( const auto & rGrabBagElement : rMap ) |
| { |
| if ( rGrabBagElement.first == "CharThemeNameAscii" && bWriteAsciiTheme ) |
| { |
| rGrabBagElement.second >>= str; |
| AddToAttrList( m_pFontsAttrList, FSNS( XML_w, XML_asciiTheme ), str ); |
| } |
| else if ( rGrabBagElement.first == "CharThemeNameCs" && bWriteCSTheme ) |
| { |
| rGrabBagElement.second >>= str; |
| AddToAttrList( m_pFontsAttrList, FSNS( XML_w, XML_cstheme ), str ); |
| } |
| else if ( rGrabBagElement.first == "CharThemeNameEastAsia" && bWriteEastAsiaTheme ) |
| { |
| rGrabBagElement.second >>= str; |
| AddToAttrList( m_pFontsAttrList, FSNS( XML_w, XML_eastAsiaTheme ), str ); |
| } |
| else if ( rGrabBagElement.first == "CharThemeNameHAnsi" && bWriteAsciiTheme ) |
| // this is not a mistake: in LibO we don't directly support the hAnsi family |
| // of attributes so we save the same value from ascii attributes instead |
| { |
| rGrabBagElement.second >>= str; |
| AddToAttrList( m_pFontsAttrList, FSNS( XML_w, XML_hAnsiTheme ), str ); |
| } |
| else if( rGrabBagElement.first == "CharThemeFontNameCs" || |
| rGrabBagElement.first == "CharThemeFontNameAscii" || |
| rGrabBagElement.first == "CharThemeFontNameEastAsia" || |
| rGrabBagElement.first == "CharThemeOriginalColor" ) |
| { |
| // just skip these, they were processed before |
| } |
| else if(rGrabBagElement.first == "CharGlowTextEffect" || |
| rGrabBagElement.first == "CharShadowTextEffect" || |
| rGrabBagElement.first == "CharReflectionTextEffect" || |
| rGrabBagElement.first == "CharTextOutlineTextEffect" || |
| rGrabBagElement.first == "CharTextFillTextEffect" || |
| rGrabBagElement.first == "CharScene3DTextEffect" || |
| rGrabBagElement.first == "CharProps3DTextEffect" || |
| rGrabBagElement.first == "CharLigaturesTextEffect" || |
| rGrabBagElement.first == "CharNumFormTextEffect" || |
| rGrabBagElement.first == "CharNumSpacingTextEffect" || |
| rGrabBagElement.first == "CharStylisticSetsTextEffect" || |
| rGrabBagElement.first == "CharCntxtAltsTextEffect") |
| { |
| beans::PropertyValue aPropertyValue; |
| rGrabBagElement.second >>= aPropertyValue; |
| m_aTextEffectsGrabBag.push_back(aPropertyValue); |
| } |
| else if (rGrabBagElement.first == "SdtEndBefore") |
| { |
| if (m_aRunSdt.m_bStartedSdt) |
| m_bEndCharSdt = true; |
| } |
| else if (rGrabBagElement.first == "SdtPr" && FLY_NOT_PROCESSED != m_nStateOfFlyFrame ) |
| { |
| const uno::Sequence<beans::PropertyValue> aGrabBagSdt = |
| rGrabBagElement.second.get< uno::Sequence<beans::PropertyValue> >(); |
| m_aRunSdt.GetSdtParamsFromGrabBag(aGrabBagSdt); |
| } |
| else |
| SAL_INFO("sw.ww8", "DocxAttributeOutput::CharGrabBag: unhandled grab bag property " << rGrabBagElement.first); |
| } |
| } |
| |
| DocxAttributeOutput::DocxAttributeOutput( DocxExport &rExport, const FSHelperPtr& pSerializer, oox::drawingml::DrawingML* pDrawingML ) |
| : AttributeOutputBase(rExport.GetFilter().getFileUrl()), |
| m_rExport( rExport ), |
| m_pSerializer( pSerializer ), |
| m_rDrawingML( *pDrawingML ), |
| m_bEndCharSdt(false), |
| m_endPageRef( false ), |
| m_pFootnotesList( new ::docx::FootnotesList() ), |
| m_pEndnotesList( new ::docx::FootnotesList() ), |
| m_footnoteEndnoteRefTag( 0 ), |
| m_pRedlineData( nullptr ), |
| m_nRedlineId( 0 ), |
| m_bOpenedSectPr( false ), |
| m_bHadSectPr(false), |
| m_bRunTextIsOn( false ), |
| m_bWritingHeaderFooter( false ), |
| m_bAnchorLinkedToNode(false), |
| m_bWritingField( false ), |
| m_bPreventDoubleFieldsHandling( false ), |
| m_nNextBookmarkId( 0 ), |
| m_nNextAnnotationMarkId( 0 ), |
| m_nEmbedFlyLevel(0), |
| m_pMoveRedlineData(nullptr), |
| m_bParagraphOpened( false ), |
| m_bParagraphFrameOpen( false ), |
| m_bIsFirstParagraph( true ), |
| m_bAlternateContentChoiceOpen( false ), |
| m_bPostponedProcessingFly( false ), |
| m_nColBreakStatus( COLBRK_NONE ), |
| m_bPostponedPageBreak( false ), |
| m_nTextFrameLevel( 0 ), |
| m_closeHyperlinkInThisRun( false ), |
| m_closeHyperlinkInPreviousRun( false ), |
| m_nFieldsInHyperlink( 0 ), |
| m_bExportingOutline(false), |
| m_nChartCount(0), |
| m_PendingPlaceholder( nullptr ), |
| m_postitFieldsMaxId( 0 ), |
| m_anchorId( 1 ), |
| m_nextFontId( 1 ), |
| m_bIgnoreNextFill(false), |
| m_pTableStyleExport(std::make_shared<DocxTableStyleExport>(rExport.m_rDoc, pSerializer)), |
| m_bParaBeforeAutoSpacing(false), |
| m_bParaAfterAutoSpacing(false), |
| m_nParaBeforeSpacing(0), |
| m_nParaAfterSpacing(0) |
| , m_nStateOfFlyFrame( FLY_NOT_PROCESSED ) |
| { |
| m_nHyperLinkCount.push_back(0); |
| } |
| |
| DocxAttributeOutput::~DocxAttributeOutput() |
| { |
| } |
| |
| DocxExport& DocxAttributeOutput::GetExport() |
| { |
| return m_rExport; |
| } |
| |
| void DocxAttributeOutput::SetSerializer( ::sax_fastparser::FSHelperPtr const & pSerializer ) |
| { |
| m_pSerializer = pSerializer; |
| m_pTableStyleExport->SetSerializer(pSerializer); |
| } |
| |
| bool DocxAttributeOutput::HasFootnotes() const |
| { |
| return !m_pFootnotesList->isEmpty(); |
| } |
| |
| bool DocxAttributeOutput::HasEndnotes() const |
| { |
| return !m_pEndnotesList->isEmpty(); |
| } |
| |
| bool DocxAttributeOutput::HasPostitFields() const |
| { |
| return !m_postitFields.empty(); |
| } |
| |
| void DocxAttributeOutput::BulletDefinition(int nId, const Graphic& rGraphic, Size aSize) |
| { |
| m_pSerializer->startElementNS(XML_w, XML_numPicBullet, |
| FSNS(XML_w, XML_numPicBulletId), OString::number(nId)); |
| |
| // Size is in twips, we need it in points. |
| OString aStyle = "width:" + OString::number(double(aSize.Width()) / 20)+ "pt;" |
| "height:" + OString::number(double(aSize.Height()) / 20) + "pt"; |
| m_pSerializer->startElementNS(XML_w, XML_pict); |
| m_pSerializer->startElementNS( XML_v, XML_shape, |
| XML_style, aStyle, |
| FSNS(XML_o, XML_bullet), "t"); |
| |
| OUString aRelId = m_rDrawingML.writeGraphicToStorage(rGraphic); |
| m_pSerializer->singleElementNS( XML_v, XML_imagedata, |
| FSNS(XML_r, XML_id), aRelId, |
| FSNS(XML_o, XML_title), ""); |
| |
| m_pSerializer->endElementNS(XML_v, XML_shape); |
| m_pSerializer->endElementNS(XML_w, XML_pict); |
| |
| m_pSerializer->endElementNS(XML_w, XML_numPicBullet); |
| } |
| |
| /* vim:set shiftwidth=4 softtabstop=4 expandtab: */ |