diff options
Diffstat (limited to 'sw/source/filter/ww8/ww8par3.cxx')
-rw-r--r-- | sw/source/filter/ww8/ww8par3.cxx | 2589 |
1 files changed, 2589 insertions, 0 deletions
diff --git a/sw/source/filter/ww8/ww8par3.cxx b/sw/source/filter/ww8/ww8par3.cxx new file mode 100644 index 0000000000..d0a294b144 --- /dev/null +++ b/sw/source/filter/ww8/ww8par3.cxx @@ -0,0 +1,2589 @@ +/* -*- 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 <string_view> +#include <svl/itemiter.hxx> +#include <vcl/svapp.hxx> +#include <vcl/outdev.hxx> +#include <sal/log.hxx> + +#include <vcl/unohelp.hxx> +#include <com/sun/star/form/XFormComponent.hpp> +#include <com/sun/star/drawing/XShape.hpp> +#include <com/sun/star/drawing/XShapes.hpp> +#include <com/sun/star/drawing/XControlShape.hpp> +#include <com/sun/star/lang/XMultiServiceFactory.hpp> +#include <com/sun/star/container/XIndexContainer.hpp> +#include <com/sun/star/text/VertOrientation.hpp> +#include <com/sun/star/text/TextContentAnchorType.hpp> +#include <com/sun/star/beans/XPropertyContainer.hpp> +#include <com/sun/star/beans/PropertyAttribute.hpp> + +#include <algorithm> +#include <hintids.hxx> +#include <editeng/fontitem.hxx> +#include <editeng/lrspitem.hxx> +#include <editeng/fhgtitem.hxx> +#include <editeng/colritem.hxx> +#include <editeng/wghtitem.hxx> +#include <editeng/crossedoutitem.hxx> +#include <editeng/udlnitem.hxx> +#include <editeng/postitem.hxx> +#include <o3tl/safeint.hxx> +#include <o3tl/temporary.hxx> +#include <unotextrange.hxx> +#include <doc.hxx> +#include <docary.hxx> +#include <IDocumentFieldsAccess.hxx> +#include <IDocumentMarkAccess.hxx> +#include <docsh.hxx> +#include <numrule.hxx> +#include <paratr.hxx> +#include <charatr.hxx> +#include <charfmt.hxx> +#include <ndtxt.hxx> +#include <expfld.hxx> +#include <fmtfld.hxx> +#include <flddropdown.hxx> +#include "sprmids.hxx" +#include "writerhelper.hxx" +#include "writerwordglue.hxx" +#include "ww8par.hxx" +#include "ww8par2.hxx" + +#include <IMark.hxx> +#include <unotools/fltrcfg.hxx> +#include <rtl/character.hxx> +#include <xmloff/odffields.hxx> +#include <comphelper/string.hxx> + +using namespace com::sun::star; +using namespace sw::util; +using namespace sw::types; +using namespace sw::mark; + +// UNO-Controls + +// OCX i.e. word 97 form controls +eF_ResT SwWW8ImplReader::Read_F_OCX( WW8FieldDesc*, OUString& ) +{ + if( m_bObj && m_nPicLocFc ) + m_nObjLocFc = m_nPicLocFc; + m_bEmbeddObj = true; + return eF_ResT::TEXT; +} + +eF_ResT SwWW8ImplReader::Read_F_FormTextBox( WW8FieldDesc* pF, OUString& rStr ) +{ + WW8FormulaEditBox aFormula(*this); + + sal_Int32 const nPos(rStr.indexOf(0x01)); + if (pF->nLCode && nPos != -1 && nPos < pF->nLCode) { + ImportFormulaControl(aFormula, pF->nSCode + nPos, WW8_CT_EDIT); + } + + /* + Here we have a small complication. This formula control contains + the default text that is displayed if you edit the form field in + the "default text" area. But MSOffice does not display that + information, instead it display the result of the field, + MSOffice just uses the default text of the control as its + initial value for the displayed default text. So we will swap in + the field result into the formula here in place of the default + text. + */ + + const SvtFilterOptions& rOpt = SvtFilterOptions::Get(); + const bool bUseEnhFields = rOpt.IsUseEnhancedFields(); + + if (!bUseEnhFields) + { + aFormula.msDefault = GetFieldResult(pF); + + SwInputField aField( + static_cast<SwInputFieldType*>(m_rDoc.getIDocumentFieldsAccess().GetSysFieldType( SwFieldIds::Input )), + aFormula.msDefault, + aFormula.msTitle, + INP_TXT, + 0 ); + aField.SetHelp(aFormula.msHelp); + aField.SetToolTip(aFormula.msToolTip); + + m_rDoc.getIDocumentContentOperations().InsertPoolItem(*m_pPaM, SwFormatField(aField)); + return eF_ResT::OK; + } + else + { + WW8PLCFx_Book* pB = m_xPlcxMan->GetBook(); + OUString aBookmarkName; + if (pB!=nullptr) { + WW8_CP currentCP=pF->nSCode; + WW8_CP currentLen=pF->nLen; + + WW8_CP nEnd; + if (o3tl::checked_add(currentCP, currentLen-1, nEnd)) { + SAL_WARN("sw.ww8", "broken offset, ignoring"); + } + else + { + sal_uInt16 bkmFindIdx; + OUString aBookmarkFind=pB->GetBookmark(currentCP-1, nEnd, bkmFindIdx); + + if (!aBookmarkFind.isEmpty()) { + pB->SetStatus(bkmFindIdx, BOOK_FIELD); // mark bookmark as consumed, such that it'll not get inserted as a "normal" bookmark again + if (!aBookmarkFind.isEmpty()) { + aBookmarkName=aBookmarkFind; + } + } + } + } + + if (pB!=nullptr && aBookmarkName.isEmpty()) { + aBookmarkName=pB->GetUniqueBookmarkName(aFormula.msTitle); + } + + if (!aBookmarkName.isEmpty()) { + m_aFieldStack.back().SetBookmarkName(aBookmarkName); + m_aFieldStack.back().SetBookmarkType(ODF_FORMTEXT); + if ( aFormula.msToolTip.getLength() < 139 ) + m_aFieldStack.back().getParameters()["Description"] <<= aFormula.msToolTip; + m_aFieldStack.back().getParameters()["Name"] <<= aFormula.msTitle; + if (aFormula.mnMaxLen && aFormula.mnMaxLen < 32768 ) + m_aFieldStack.back().getParameters()["MaxLength"] <<= aFormula.mnMaxLen; + + if ( aFormula.mfType == 1 ) + m_aFieldStack.back().getParameters()["Type"] <<= OUString("number"); + else if ( aFormula.mfType == 2 ) + m_aFieldStack.back().getParameters()["Type"] <<= OUString("date"); + else if ( aFormula.mfType == 3 ) + m_aFieldStack.back().getParameters()["Type"] <<= OUString("currentTime"); + else if ( aFormula.mfType == 4 ) + m_aFieldStack.back().getParameters()["Type"] <<= OUString("currentDate"); + else if ( aFormula.mfType == 5 ) + m_aFieldStack.back().getParameters()["Type"] <<= OUString("calculated"); + } + return eF_ResT::TEXT; + } +} + +eF_ResT SwWW8ImplReader::Read_F_FormCheckBox( WW8FieldDesc* pF, OUString& rStr ) +{ + WW8FormulaCheckBox aFormula(*this); + + if (!m_xFormImpl) + m_xFormImpl.reset(new SwMSConvertControls(m_pDocShell, m_pPaM)); + + if (rStr[pF->nLCode-1]==0x01) + ImportFormulaControl(aFormula,pF->nSCode+pF->nLCode-1, WW8_CT_CHECKBOX); + const SvtFilterOptions& rOpt = SvtFilterOptions::Get(); + const bool bUseEnhFields = rOpt.IsUseEnhancedFields(); + + if (!bUseEnhFields) + { + m_xFormImpl->InsertFormula(aFormula); + return eF_ResT::OK; + } + + OUString aBookmarkName; + WW8PLCFx_Book* pB = m_xPlcxMan->GetBook(); + if (pB!=nullptr) { + WW8_CP currentCP=pF->nSCode; + WW8_CP currentLen=pF->nLen; + + sal_uInt16 bkmFindIdx; + OUString aBookmarkFind=pB->GetBookmark(currentCP-1, currentCP+currentLen-1, bkmFindIdx); + + if (!aBookmarkFind.isEmpty()) { + pB->SetStatus(bkmFindIdx, BOOK_FIELD); // mark as consumed by field + if (!aBookmarkFind.isEmpty()) { + aBookmarkName=aBookmarkFind; + } + } + } + + if (pB!=nullptr && aBookmarkName.isEmpty()) { + aBookmarkName=pB->GetUniqueBookmarkName(aFormula.msTitle); + } + + if (!aBookmarkName.isEmpty()) + { + IDocumentMarkAccess* pMarksAccess = m_rDoc.getIDocumentMarkAccess( ); + IFieldmark* pFieldmark = pMarksAccess->makeNoTextFieldBookmark( + *m_pPaM, aBookmarkName, ODF_FORMCHECKBOX ); + OSL_ENSURE(pFieldmark!=nullptr, "hmmm; why was the bookmark not created?"); + if (pFieldmark!=nullptr) { + IFieldmark::parameter_map_t* const pParameters = pFieldmark->GetParameters(); + ICheckboxFieldmark* pCheckboxFm = dynamic_cast<ICheckboxFieldmark*>(pFieldmark); + (*pParameters)[ODF_FORMCHECKBOX_HELPTEXT] <<= aFormula.msToolTip; + + if(pCheckboxFm) + pCheckboxFm->SetChecked(aFormula.mnChecked != 0); + // set field data here... + } + } + return eF_ResT::OK; +} + +eF_ResT SwWW8ImplReader::Read_F_FormListBox( WW8FieldDesc* pF, OUString& rStr) +{ + WW8FormulaListBox aFormula(*this); + + if (pF->nLCode > 0 && rStr.getLength() >= pF->nLCode && rStr[pF->nLCode-1] == 0x01) + ImportFormulaControl(aFormula,pF->nSCode+pF->nLCode-1, WW8_CT_DROPDOWN); + + const SvtFilterOptions& rOpt = SvtFilterOptions::Get(); + bool bUseEnhFields = rOpt.IsUseEnhancedFields(); + + if (!bUseEnhFields) + { + SwDropDownField aField(static_cast<SwDropDownFieldType*>(m_rDoc.getIDocumentFieldsAccess().GetSysFieldType(SwFieldIds::Dropdown))); + + aField.SetName(aFormula.msTitle); + aField.SetHelp(aFormula.msHelp); + aField.SetToolTip(aFormula.msToolTip); + + if (!aFormula.maListEntries.empty()) + { + aField.SetItems(std::vector(aFormula.maListEntries)); + int nIndex = aFormula.mfDropdownIndex < aFormula.maListEntries.size() ? aFormula.mfDropdownIndex : 0; + aField.SetSelectedItem(aFormula.maListEntries[nIndex]); + } + + m_rDoc.getIDocumentContentOperations().InsertPoolItem(*m_pPaM, SwFormatField(aField)); + return eF_ResT::OK; + } + else + { + // TODO: review me + OUString aBookmarkName; + WW8PLCFx_Book* pB = m_xPlcxMan->GetBook(); + if (pB!=nullptr) + { + WW8_CP currentCP=pF->nSCode; + WW8_CP currentLen=pF->nLen; + + sal_uInt16 bkmFindIdx; + OUString aBookmarkFind=pB->GetBookmark(currentCP-1, currentCP+currentLen-1, bkmFindIdx); + + if (!aBookmarkFind.isEmpty()) + { + pB->SetStatus(bkmFindIdx, BOOK_FIELD); // mark as consumed by field + if (!aBookmarkFind.isEmpty()) + aBookmarkName=aBookmarkFind; + } + } + + if (pB!=nullptr && aBookmarkName.isEmpty()) + aBookmarkName=pB->GetUniqueBookmarkName(aFormula.msTitle); + + if (!aBookmarkName.isEmpty()) + { + IDocumentMarkAccess* pMarksAccess = m_rDoc.getIDocumentMarkAccess( ); + IFieldmark *pFieldmark = + pMarksAccess->makeNoTextFieldBookmark( *m_pPaM, aBookmarkName, ODF_FORMDROPDOWN ); + OSL_ENSURE(pFieldmark!=nullptr, "hmmm; why was the bookmark not created?"); + if ( pFieldmark != nullptr ) + { + uno::Sequence< OUString > vListEntries(aFormula.maListEntries.size()); + std::copy(aFormula.maListEntries.begin(), aFormula.maListEntries.end(), vListEntries.getArray()); + (*pFieldmark->GetParameters())[ODF_FORMDROPDOWN_LISTENTRY] <<= vListEntries; + sal_Int32 nIndex = aFormula.mfDropdownIndex < aFormula.maListEntries.size() ? aFormula.mfDropdownIndex : -1; + if (nIndex >= 0) + (*pFieldmark->GetParameters())[ODF_FORMDROPDOWN_RESULT] <<= nIndex; + // set field data here... + } + } + + return eF_ResT::OK; + } +} + +eF_ResT SwWW8ImplReader::Read_F_HTMLControl(WW8FieldDesc*, OUString&) +{ + if( m_bObj && m_nPicLocFc ) + m_nObjLocFc = m_nPicLocFc; + m_bEmbeddObj = true; + return eF_ResT::TEXT; +} + +// Helper declarations + +// Style Id's for each level +typedef sal_uInt16 WW8aIdSty[WW8ListManager::nMaxLevel]; +// Character Style Pointer +typedef SwCharFormat* WW8aCFormat[WW8ListManager::nMaxLevel]; + +namespace { + +struct WW8LST // only THOSE entries, WE need! +{ + WW8aIdSty aIdSty; // Style Id's for each level, + // nIStDNil if no style linked + sal_uInt32 nIdLst; // Unique List ID + sal_uInt32 nTplC; // Unique template code - What is this? + bool bSimpleList:1; // Flag: List only has ONE level + bool bRestartHdn:1; // WW6-Compatibility-Flag: + // true if the list should start numbering over +}; // at the beginning of each section + +} + +const sal_uInt32 cbLSTF=28; + +namespace { + +struct WW8LFO // only THOSE entries, WE need! +{ + SwNumRule* pNumRule; // Parent NumRule + sal_uInt32 nIdLst; // Unique List ID + sal_uInt8 nLfoLvl; // count of levels whose format is overridden + bool bSimpleList; +}; + +struct WW8LVL // only THE entries, WE need! +{ + sal_Int32 nStartAt; // start at value for this value + sal_Int32 nV6DxaSpace;// Ver6-Compatible: min Space between Num and text::Paragraph + sal_Int32 nV6Indent; // Ver6-Compatible: Width of prefix text; + // Use definition of first line indent if appropriate! + // Paragraph attributes from GrpprlPapx + sal_uInt16 nDxaLeft; // left indent + short nDxaLeft1; // first line indent + + sal_uInt8 nNFC; // number format code + // Offset of fieldcodes in Num-X-String + sal_uInt8 aOfsNumsXCH[WW8ListManager::nMaxLevel]; + sal_uInt8 nLenGrpprlChpx; // length, in bytes, of the LVL's grpprlChpx + sal_uInt8 nLenGrpprlPapx; // length, in bytes, of the LVL's grpprlPapx + sal_uInt8 nAlign; // alignment (left, right, centered) of the number + bool bV6Prev; // Ver6-Compatible: number will include previous levels + bool bV6PrSp; // Ver6-Compatible: doesn't matter + bool bV6; // if true, pay attention to the V6-Compatible Entries! + +}; + +struct WW8LFOLVL +{ + sal_Int32 nStartAt; // start-at value if bFormat==false and bStartAt == true + // (if bFormat==true, the start-at is stored in the LVL) + sal_uInt8 nLevel; // the level to be overridden + // this byte has not been packed into the following byte on _purpose_ !! + // (see comment of struct WW8LFOInfo) + + bool bStartAt :1; // true if the start-at value is overridden + bool bFormat :1; // true if the formatting is overridden + + WW8LFOLVL() : + nStartAt(1), nLevel(0), bStartAt(true), bFormat(false) {} +}; + +} + +// Data to be saved in ListInfo + +struct WW8LSTInfo // sorted by nIdLst (in WW8 used list-Id) +{ + std::vector<ww::bytes> maParaSprms; + WW8aIdSty aIdSty; // Style Id's for each level + WW8aCFormat aCharFormat = {}; // Character Style Pointer + + SwNumRule* pNumRule; // Pointer to list-template in Writer + sal_uInt32 nIdLst; // WW8Id of this list + bool bSimpleList:1;// Flag, if this NumRule only uses one Level + bool bUsedInDoc :1;// Flag, if this NumRule is used in the Doc, + // or is supposed to be deleted on Reader-End + + WW8LSTInfo(SwNumRule* pNumRule_, const WW8LST& aLST) + : pNumRule(pNumRule_), nIdLst(aLST.nIdLst), + bSimpleList(aLST.bSimpleList), bUsedInDoc(false) + { + memcpy( aIdSty, aLST.aIdSty, sizeof( aIdSty )); + } + +}; + +// Data to be saved in ListenFormatOverrideInfos + +struct WW8LFOInfo // unordered, means ordered like in WW8 Stream +{ + std::vector<ww::bytes> maParaSprms; + std::vector<WW8LFOLVL> maOverrides; + SwNumRule* pNumRule; // Pointer to list template in Writer + // either List in LSTInfos or own List + // (in Ctor use the list from LSTInfos first) + + sal_uInt32 nIdLst; // WW8-Id of the relevant list + sal_uInt8 nLfoLvl; // count of levels whose format is overridden + // yes we could include nLfoLvl (via :4) into the following byte, + // but it probably would be a source of error once MS increases their Listformat + // to more than 15 levels + + bool bOverride :1;// Flag if NumRule is not included in maLSTInfos, + // but was created for m_LFOInfos + bool bUsedInDoc :1;// Flag if NumRule is used in Doc, + // or should be deleted on Reader-End + bool bLSTbUIDSet :1;// Flag, if bUsedInDoc is set in maLSTInfos + + explicit WW8LFOInfo(const WW8LFO& rLFO); +}; + +WW8LFOInfo::WW8LFOInfo(const WW8LFO& rLFO) + : maParaSprms(WW8ListManager::nMaxLevel) + , maOverrides(WW8ListManager::nMaxLevel) + , pNumRule(rLFO.pNumRule) + , nIdLst(rLFO.nIdLst) + , nLfoLvl(rLFO.nLfoLvl) + , bOverride(rLFO.nLfoLvl != 0) + , bUsedInDoc(false) + , bLSTbUIDSet(false) +{ +} + +// Helper methods + +// find Sprm-Parameter-Data, if Sprm is included in Grpprl +SprmResult WW8ListManager::GrpprlHasSprm(sal_uInt16 nId, sal_uInt8& rSprms, + sal_uInt8 nLen) +{ + return maSprmParser.findSprmData(nId, &rSprms, nLen); +} + +namespace { + +class ListWithId +{ +private: + sal_uInt32 mnIdLst; +public: + explicit ListWithId(sal_uInt32 nIdLst) : mnIdLst(nIdLst) {} + bool operator() (const std::unique_ptr<WW8LSTInfo>& pEntry) const + { return (pEntry->nIdLst == mnIdLst); } +}; + +} + +// Access via List-Id of LST Entry +WW8LSTInfo* WW8ListManager::GetLSTByListId( sal_uInt32 nIdLst ) const +{ + auto aResult = + std::find_if(maLSTInfos.begin(),maLSTInfos.end(),ListWithId(nIdLst)); + if (aResult == maLSTInfos.end()) + return nullptr; + return aResult->get(); +} + +SvxNumType WW8ListManager::GetSvxNumTypeFromMSONFC(sal_uInt16 nNFC) +{ + SvxNumType nType(SVX_NUM_ARABIC); + + switch (nNFC) + { + case 0: + nType = SVX_NUM_ARABIC; + break; + case 1: + nType = SVX_NUM_ROMAN_UPPER; + break; + case 2: + nType = SVX_NUM_ROMAN_LOWER; + break; + case 3: + nType = SVX_NUM_CHARS_UPPER_LETTER_N; + break; + case 4: + nType = SVX_NUM_CHARS_LOWER_LETTER_N; + break; + case 5: // ordinal + nType = SVX_NUM_TEXT_NUMBER; + break; + case 6: // cardinalText + nType = SVX_NUM_TEXT_CARDINAL; + break; + case 7: // ordinalText + nType = SVX_NUM_TEXT_ORDINAL; + break; + //case 8: // hex + + case 9: + // 0x09, msonfcChiManSty + nType = SVX_NUM_SYMBOL_CHICAGO; + break; + //case 15: // decimalHalfWidth + //case 17: // japaneseDigitalTenThousand + + case 18: // decimalEnclosedCircle + case 28: // decimalEnclosedCircleChinese + case 29: // ideographEnclosedCircle + nType = SVX_NUM_CIRCLE_NUMBER; + break; + case 22: + // 0x16, msonfcArabicLZ + nType = SVX_NUM_ARABIC_ZERO; + break; + case 23: + nType = SVX_NUM_CHAR_SPECIAL; + + break; + case 255: + nType = SVX_NUM_NUMBER_NONE; + break; + case 14: + case 19: + nType = SVX_NUM_FULL_WIDTH_ARABIC; + break; + case 30: + nType = SVX_NUM_TIAN_GAN_ZH; + break; + case 31: // ideographZodiac + case 32: // ideographZodiacTraditional + nType = SVX_NUM_DI_ZI_ZH; + break; + case 33: // taiwaneseCounting + case 35: + case 36: + case 37: + case 11: + case 39: + nType = SVX_NUM_NUMBER_LOWER_ZH; + break; + case 34: + nType = SVX_NUM_NUMBER_UPPER_ZH_TW; + break; + case 38: + nType = SVX_NUM_NUMBER_UPPER_ZH; + break; + case 10: + case 16: // japaneseLegal + nType = SVX_NUM_NUMBER_TRADITIONAL_JA; + break; + case 20: + nType = SVX_NUM_AIU_FULLWIDTH_JA; + break; + case 12: + nType = SVX_NUM_AIU_HALFWIDTH_JA; + break; + case 21: + nType = SVX_NUM_IROHA_FULLWIDTH_JA; + break; + case 13: + nType = SVX_NUM_IROHA_HALFWIDTH_JA; + break; + case 24: + nType = SVX_NUM_HANGUL_SYLLABLE_KO; + break; + case 25: + nType = SVX_NUM_HANGUL_JAMO_KO; + break; + //case 26: // decimalEnclosedFullstop + //case 27: // decimalEnclosedParen + //case 40: // decimal (Chinese) + + case 41: // koreanDigital + case 42: // koreanCounting + case 43: // koreanLegal + nType = SVX_NUM_NUMBER_HANGUL_KO; + break; + case 44: // koreanDigital2 + nType = SVX_NUM_NUMBER_UPPER_KO; + break; + case 45: // hebrew1 + nType = SVX_NUM_NUMBER_HEBREW; + break; + case 46: // arabicAlpha + nType = SVX_NUM_CHARS_ARABIC; + break; + case 47: // hebrew2 + nType = SVX_NUM_CHARS_HEBREW; + break; + case 48: // arabicAbjad + nType = SVX_NUM_CHARS_ARABIC_ABJAD; + break; + case 49: // hindiVowels + nType = SVX_NUM_CHARS_NEPALI; + break; + //case 50: // hindiConsonants + //case 51: // hindiNumbers + //case 52: // hindiCounting + + case 53: // thaiLetters + nType = SVX_NUM_CHARS_THAI; + break; + //case 54: // thaiNumbers + //case 55: // thaiCounting + //case 56: // vietnameseCounting + //case 57: // numberInDash + + case 58: // russianLower + nType = SVX_NUM_CHARS_CYRILLIC_LOWER_LETTER_RU; + break; + case 59: // russianUpper + nType =SVX_NUM_CHARS_CYRILLIC_UPPER_LETTER_RU; + break; + default: + nType = SVX_NUM_ARABIC; + break; + } + + return nType; +} + +bool WW8ListManager::ReadLVL(SwNumFormat& rNumFormat, std::unique_ptr<SfxItemSet>& rpItemSet, + sal_uInt16 nLevelStyle, bool bSetStartNo, sal_uInt16 /*nLevel*/, ww::bytes &rParaSprms) +{ + sal_uInt8 aBits1(0); + SvxNumType nType(SVX_NUM_ARABIC); + SvxAdjust eAdj; // Alignment (Left/right/centered) + sal_UCS4 cBullet(0x2190); // default safe bullet + + sal_Unicode cGrfBulletCP(USHRT_MAX); + + WW8LVL aLVL = {}; + + // 1. read LVLF + + m_rSt.ReadInt32( aLVL.nStartAt ); + m_rSt.ReadUChar( aLVL.nNFC ); + m_rSt.ReadUChar( aBits1 ); + if( ERRCODE_NONE != m_rSt.GetError() ) return false; + aLVL.nAlign = (aBits1 & 0x03); + if( aBits1 & 0x10 ) aLVL.bV6Prev = true; + if( aBits1 & 0x20 ) aLVL.bV6PrSp = true; + if( aBits1 & 0x40 ) aLVL.bV6 = true; + bool bLVLOkB = true; + for(sal_uInt8 nLevelB = 0; nLevelB < nMaxLevel; ++nLevelB) + { + m_rSt.ReadUChar( aLVL.aOfsNumsXCH[ nLevelB ] ); + if( ERRCODE_NONE != m_rSt.GetError() ) + { + bLVLOkB = false; + break; + } + } + + if( !bLVLOkB ) + return false; + + sal_uInt8 ixchFollow(0); + m_rSt.ReadUChar( ixchFollow ); + m_rSt.ReadInt32( aLVL.nV6DxaSpace ); + m_rSt.ReadInt32( aLVL.nV6Indent ); + m_rSt.ReadUChar( aLVL.nLenGrpprlChpx ); + m_rSt.ReadUChar( aLVL.nLenGrpprlPapx ); + m_rSt.SeekRel( 2 ); + if( ERRCODE_NONE != m_rSt.GetError()) return false; + + // 2. read PAPx if needed and search for indent values + + short nTabPos = 0; // #i86652# - read tab setting + if( aLVL.nLenGrpprlPapx ) + { + sal_uInt8 aGrpprlPapx[ 255 ]; + if (aLVL.nLenGrpprlPapx != m_rSt.ReadBytes(&aGrpprlPapx, aLVL.nLenGrpprlPapx)) + return false; + // "sprmPDxaLeft" pap.dxaLeft;dxa;word; + SprmResult aSprm = GrpprlHasSprm(0x840F,aGrpprlPapx[0],aLVL.nLenGrpprlPapx); + if (!aSprm.pSprm) + aSprm = GrpprlHasSprm(0x845E,aGrpprlPapx[0],aLVL.nLenGrpprlPapx); + + if (aSprm.pSprm && aSprm.nRemainingData >= 2) + { + const sal_uInt8 *pBegin = aSprm.pSprm - 2; + for(int i=0;i<4;++i) + rParaSprms.push_back(*pBegin++); + short nDxaLeft = SVBT16ToUInt16(aSprm.pSprm); + aLVL.nDxaLeft = (0 < nDxaLeft) ? o3tl::narrowing<sal_uInt16>(nDxaLeft) + : o3tl::narrowing<sal_uInt16>(-nDxaLeft); + } + + // "sprmPDxaLeft1" pap.dxaLeft1;dxa;word; + aSprm = GrpprlHasSprm(0x8411,aGrpprlPapx[0],aLVL.nLenGrpprlPapx); + if (!aSprm.pSprm) + aSprm = GrpprlHasSprm(0x8460,aGrpprlPapx[0],aLVL.nLenGrpprlPapx); + + if (aSprm.pSprm && aSprm.nRemainingData >= 2) + { + const sal_uInt8 *pBegin = aSprm.pSprm - 2; + for(int i=0;i<4;++i) + rParaSprms.push_back(*pBegin++); + aLVL.nDxaLeft1 = SVBT16ToUInt16(aSprm.pSprm); + } + + // #i86652# - read tab setting + aSprm = GrpprlHasSprm(0xC615,aGrpprlPapx[0],aLVL.nLenGrpprlPapx); + const sal_uInt8* pSprm = aSprm.pSprm; + if (pSprm && aSprm.nRemainingData >= 5) + { + bool bDone = false; + if (*(pSprm-1) == 5) + { + if (*pSprm++ == 0) //nDel + { + if (*pSprm++ == 1) //nIns + { + nTabPos = SVBT16ToUInt16(pSprm); + pSprm+=2; + if (*pSprm == 6) //type + { + bDone = true; + } + } + } + } + OSL_ENSURE(bDone, "tab setting in numbering is " + "of unexpected configuration"); + } + if ( rNumFormat.GetPositionAndSpaceMode() == + SvxNumberFormat::LABEL_WIDTH_AND_POSITION ) + { + // If there is a tab setting with a larger value, then use that. + // Ideally we would allow tabs to be used in numbering fields and set + // this on the containing paragraph which would make it actually work + // most of the time. + if ( nTabPos != 0 ) + { + const sal_uInt16 nDesired = aLVL.nDxaLeft + aLVL.nDxaLeft1; + + bool bDoAdjust = false; + if ( nDesired < aLVL.nDxaLeft ) + { + if ( nDesired < nTabPos && nTabPos < aLVL.nDxaLeft ) + { + bDoAdjust = true; + } + } + else + { + if ( aLVL.nDxaLeft < nTabPos && nTabPos < nDesired ) + { + bDoAdjust = true; + } + } + + if (bDoAdjust) + { + aLVL.nDxaLeft = (0 < nTabPos) + ? o3tl::narrowing<sal_uInt16>(nTabPos) + : o3tl::narrowing<sal_uInt16>(-nTabPos); + + aLVL.nDxaLeft1 = nDesired - aLVL.nDxaLeft; + } + } + } + } + + // 3. read CHPx if needed + + sal_uInt16 nWitchPicIsBullet = USHRT_MAX; + bool bIsPicBullet = false; + + if( aLVL.nLenGrpprlChpx ) + { + sal_uInt8 aGrpprlChpx[ 255 ] = {}; + if (aLVL.nLenGrpprlChpx != m_rSt.ReadBytes(&aGrpprlChpx, aLVL.nLenGrpprlChpx)) + return false; + + //For i120928,parse the graphic info of bullets + SprmResult aSprmWhichPis = GrpprlHasSprm(NS_sprm::CPbiIBullet::val, aGrpprlChpx[0],aLVL.nLenGrpprlChpx); + SprmResult aSprmIsPicBullet = GrpprlHasSprm(NS_sprm::CPbiGrf::val, aGrpprlChpx[0],aLVL.nLenGrpprlChpx); + if (aSprmWhichPis.pSprm && aSprmWhichPis.nRemainingData >= 1) + { + nWitchPicIsBullet = *aSprmWhichPis.pSprm; + } + if (aSprmIsPicBullet.pSprm && aSprmIsPicBullet.nRemainingData >= 1) + { + bIsPicBullet = (*aSprmIsPicBullet.pSprm) & 0x0001; + } + + // create new Itemset for character attributes + rpItemSet.reset(new SfxItemSetFixed<RES_CHRATR_BEGIN, RES_CHRATR_END - 1>( m_rDoc.GetAttrPool() )); + + // Set Reader-ItemSet-Pointer to the newly created set + m_rReader.SetCurrentItemSet(std::move(rpItemSet)); + // Set Reader-Style to Style of this Level + sal_uInt16 nOldColl = m_rReader.GetCurrentColl(); + sal_uInt16 nNewColl = nLevelStyle; + if (ww::stiNil == nNewColl) + nNewColl = 0; + m_rReader.SetNCurrentColl( nNewColl ); + + // The Read_xy() methods in WW8PAR6.cxx are calling their respective + // NewAttr() or GetFormatAttr() which can determine, by using the assigned + // Reader-ItemSet-Pointer, whether this specific ItemSet is relevant + // and not a Stack or Style! + sal_uInt16 nOldFlags1 = m_rReader.GetToggleAttrFlags(); + sal_uInt16 nOldFlags2 = m_rReader.GetToggleBiDiAttrFlags(); + + WW8SprmIter aSprmIter(&aGrpprlChpx[0], aLVL.nLenGrpprlChpx, + maSprmParser); + while (const sal_uInt8* pSprm = aSprmIter.GetSprms()) + { + m_rReader.ImportSprm(pSprm, aSprmIter.GetRemLen(), aSprmIter.GetCurrentId()); + aSprmIter.advance(); + } + + // Reset Reader-ItemSet-Pointer and Reader-Style + rpItemSet = m_rReader.SetCurrentItemSet(nullptr); + m_rReader.SetNCurrentColl( nOldColl ); + m_rReader.SetToggleAttrFlags(nOldFlags1); + m_rReader.SetToggleBiDiAttrFlags(nOldFlags2); + } + + // 4. Read numbering String. Results in prefix and postfix + + OUString sNumString(comphelper::string::sanitizeStringSurrogates(read_uInt16_PascalString(m_rSt))); + + // 5. convert read values into Writer syntax + + nType = GetSvxNumTypeFromMSONFC(aLVL.nNFC); + //For i120928,type info + if (bIsPicBullet) + { + nType = SVX_NUM_BITMAP; + } + + if (style::NumberingType::CHAR_SPECIAL == nType) + { + cBullet = !sNumString.isEmpty() + ? sNumString.iterateCodePoints(&o3tl::temporary(sal_Int32(0))) : 0x2190; + + if (!cBullet) // unsave control code? + cBullet = 0x2190; + } + else if (style::NumberingType::BITMAP == nType) //For i120928,position index info of graphic + { + cGrfBulletCP = nWitchPicIsBullet; // This is a bullet picture ID + } + + switch( aLVL.nAlign ) + { + case 0: + eAdj = SvxAdjust::Left; + break; + case 1: + eAdj = SvxAdjust::Center; + break; + case 2: + eAdj = SvxAdjust::Right; + break; + case 3: + // Writer here cannot do block justification + eAdj = SvxAdjust::Left; + break; + default: + // undefined value + OSL_ENSURE( false, "Value of aLVL.nAlign is not supported" ); + // take default + eAdj = SvxAdjust::Left; + break; + } + + // 6. Configure NumFormat + if( bSetStartNo && 0 <= aLVL.nStartAt) + rNumFormat.SetStart(o3tl::narrowing<sal_uInt16>(aLVL.nStartAt)); + rNumFormat.SetNumberingType( nType ); + rNumFormat.SetNumAdjust( eAdj ); + + if( style::NumberingType::CHAR_SPECIAL == nType ) + { + // first character of the Prefix-Text is the Bullet + rNumFormat.SetBulletChar(cBullet); + // Don't forget: further below, after building styles + // Call SetBulletFont() !!! + } + //For i120928,position index info + else if (style::NumberingType::BITMAP == nType) + { + rNumFormat.SetGrfBulletCP(cGrfBulletCP); + } + else + { + // Replace symbols at aOfsNumsXCH offsets to %1%, %2% as supported by LO + OUString sListFormat = sNumString; + if (sListFormat.getLength()) + { + sal_uInt32 nExtraOffset = 0; + sal_uInt8 nLevelB = 0; + while (nLevelB < nMaxLevel && aLVL.aOfsNumsXCH[nLevelB]) + { + // Replacement symbol is read from source string from position taken from aOfsNumsXCH array + sal_uInt8 nOffset = aLVL.aOfsNumsXCH[nLevelB] + nExtraOffset - 1; + if (nOffset >= sListFormat.getLength()) + { + SAL_WARN("sw.ww8", "List level reference is beyond the border. Ignored."); + nLevelB++; + continue; + } + sal_uInt8 nReplacement = sListFormat[nOffset] + 1; + + OUString sReplacement("%" + OUString::number(nReplacement) + "%"); + sListFormat = sListFormat.replaceAt(nOffset, 1, sReplacement); + + // We need also update an offset, since we are replacing one symbol by at least two + nExtraOffset += sReplacement.getLength() - 1; + nLevelB++; + } + } + + rNumFormat.SetListFormat(sListFormat); + + // Total count of replacement holders is determining amount of required parent numbering to include + // TODO: not sure how "%" symbol is escaped. This is not supported yet + sal_Int16 nParentNum = comphelper::string::getTokenCount(sListFormat, '%'); + rNumFormat.SetIncludeUpperLevels(nParentNum); + } + + // #i89181# + if ( rNumFormat.GetPositionAndSpaceMode() == + SvxNumberFormat::LABEL_WIDTH_AND_POSITION ) + { + if (eAdj == SvxAdjust::Right) + { + rNumFormat.SetAbsLSpace(aLVL.nDxaLeft); + rNumFormat.SetFirstLineOffset(-aLVL.nDxaLeft); + rNumFormat.SetCharTextDistance(-aLVL.nDxaLeft1); + } + else + { + rNumFormat.SetAbsLSpace( aLVL.nDxaLeft ); + rNumFormat.SetFirstLineOffset(aLVL.nDxaLeft1); + } + } + else + { + rNumFormat.SetIndentAt( aLVL.nDxaLeft ); + rNumFormat.SetFirstLineIndent(aLVL.nDxaLeft1); + if ( !aLVL.bV6 ) + rNumFormat.SetListtabPos( nTabPos ); + else + rNumFormat.SetListtabPos( aLVL.nV6Indent ); + SvxNumberFormat::LabelFollowedBy eNumLabelFollowedBy = SvxNumberFormat::LISTTAB; + switch ( ixchFollow ) + { + case 0: + { + eNumLabelFollowedBy = SvxNumberFormat::LISTTAB; + } + break; + case 1: + { + eNumLabelFollowedBy = SvxNumberFormat::SPACE; + } + break; + case 2: + { + eNumLabelFollowedBy = SvxNumberFormat::NOTHING; + } + break; + } + rNumFormat.SetLabelFollowedBy( eNumLabelFollowedBy ); + } + + return true; +} + +void WW8ListManager::AdjustLVL( sal_uInt8 nLevel, SwNumRule& rNumRule, + WW8aISet const & rListItemSet, WW8aCFormat& rCharFormat, bool& bNewCharFormatCreated, + const OUString& sPrefix ) +{ + bNewCharFormatCreated = false; + sal_uInt8 nIdenticalItemSetLevel; + const SfxPoolItem* pItem; + + SwNumFormat aNumFormat = rNumRule.Get( nLevel ); + + SfxItemSet* pThisLevelItemSet = rListItemSet[nLevel].get(); + + if( pThisLevelItemSet && pThisLevelItemSet->Count()) + { + nIdenticalItemSetLevel = nMaxLevel; + SfxItemIter aIter( *pThisLevelItemSet ); + for (sal_uInt8 nLowerLevel = 0; nLowerLevel < nLevel; ++nLowerLevel) + { + SfxItemSet* pLowerLevelItemSet = rListItemSet[nLowerLevel].get(); + if( pLowerLevelItemSet + && (pLowerLevelItemSet->Count() == pThisLevelItemSet->Count()) ) + { + nIdenticalItemSetLevel = nLowerLevel; + const SfxPoolItem* pItemIter = aIter.GetCurItem(); + do + { + if( // search for appropriate pItem in pLowerLevelItemSet + (SfxItemState::SET != pLowerLevelItemSet->GetItemState( + pItemIter->Which(), false, &pItem ) ) + || // use virtual "!=" Operator + (*pItem != *pItemIter) ) + // if no Item with equal nWhich was found or Item value was not equal + // store inequality and break! + { + nIdenticalItemSetLevel = nMaxLevel; + break; + } + pItemIter = aIter.NextItem(); + } while (pItemIter); + + if( nIdenticalItemSetLevel != nMaxLevel ) + break; + } + } + + SwCharFormat* pFormat; + if (nMaxLevel == nIdenticalItemSetLevel) + { + // Define Style + const OUString aName( (!sPrefix.isEmpty() ? sPrefix : rNumRule.GetName()) + + "z" + OUString::number( nLevel ) ); + + // remove const by casting + pFormat = m_rDoc.MakeCharFormat(aName, m_rDoc.GetDfltCharFormat()); + bNewCharFormatCreated = true; + // Set Attributes + pFormat->SetFormatAttr( *pThisLevelItemSet ); + } + else + { + // append Style + pFormat = rCharFormat[ nIdenticalItemSetLevel ]; + } + + // store + rCharFormat[ nLevel ] = pFormat; + + // Append Style to NumFormat + + aNumFormat.SetCharFormat( pFormat ); + } + + // if necessary: Append Bullet Font to NumFormat + + if( SVX_NUM_CHAR_SPECIAL == aNumFormat.GetNumberingType() ) + { + SwCharFormat* pFormat = aNumFormat.GetCharFormat(); + vcl::Font aFont; + if( !pFormat ) + { + aFont = numfunc::GetDefBulletFont(); + } + else + { + const SvxFontItem& rFontItem = pFormat->GetFont(); + aFont.SetFamily( rFontItem.GetFamily() ); + aFont.SetFamilyName( rFontItem.GetFamilyName() ); + aFont.SetStyleName( rFontItem.GetStyleName() ); + aFont.SetPitch( rFontItem.GetPitch() ); + aFont.SetCharSet( rFontItem.GetCharSet() ); + } + aNumFormat.SetBulletFont( &aFont ); + } + + // Set NumFormat in NumRule + + rNumRule.Set(nLevel, aNumFormat); +} + +SwNumRule* WW8ListManager::CreateNextRule(bool bSimple) +{ + // Used to build the Style Name + const OUString sPrefix("WW8Num" + OUString::number(m_nUniqueList++)); + // #i86652# + sal_uInt16 nRul = + m_rDoc.MakeNumRule( m_rDoc.GetUniqueNumRuleName(&sPrefix), nullptr, false, + SvxNumberFormat::LABEL_ALIGNMENT ); + SwNumRule* pMyNumRule = m_rDoc.GetNumRuleTable()[nRul]; + pMyNumRule->SetAutoRule(false); + pMyNumRule->SetContinusNum(bSimple); + return pMyNumRule; +} + +SwNumRule* WW8ListManager::GetNumRule(size_t i) +{ + if (i < maLSTInfos.size()) + return maLSTInfos[i]->pNumRule; + else + return nullptr; +} + +// public methods + +WW8ListManager::WW8ListManager(SvStream& rSt_, SwWW8ImplReader& rReader_) + : maSprmParser(rReader_.GetFib()), m_rReader(rReader_) + , m_rDoc(m_rReader.GetDoc()) + , m_rFib(m_rReader.GetFib()), m_rSt(rSt_) + , m_nUniqueList(1) + , m_nLastLFOPosition(USHRT_MAX) +{ + + // LST and LFO only since WW8 + if( ( 8 > m_rFib.m_nVersion ) + || ( m_rFib.m_fcPlcfLst == m_rFib.m_fcPlfLfo ) + || ( m_rFib.m_lcbPlcfLst < 2 ) + || ( m_rFib.m_lcbPlfLfo < 2) ) return; // no public lists + + // create Arrays + bool bLVLOk = true; + + sal_uInt64 nOriginalPos = m_rSt.Tell(); + + // 1. read PLCF LST and create list templates in Writer + + bool bOk = checkSeek(m_rSt, m_rFib.m_fcPlcfLst); + + if (!bOk) + return; + + sal_uInt32 nRemainingPlcfLst = m_rFib.m_lcbPlcfLst; + + sal_uInt16 nListCount(0); + m_rSt.ReadUInt16( nListCount ); + nRemainingPlcfLst -= 2; + bOk = nListCount > 0; + + if (!bOk) + return; + + // 1.1 read all LST + const size_t nMinRecordSize = 10 + 2*nMaxLevel; + const size_t nMaxRecords = m_rSt.remainingSize() / nMinRecordSize; + if (nListCount > nMaxRecords) + { + SAL_WARN("sw.ww8", "Parsing error: " << nMaxRecords << + " max possible entries, but " << nListCount << " claimed, truncating"); + nListCount = nMaxRecords; + } + for (sal_uInt16 nList=0; nList < nListCount; ++nList) + { + if (nRemainingPlcfLst < cbLSTF) + break; + + WW8LST aLST = {}; + + // 1.1.1 read Data + + m_rSt.ReadUInt32( aLST.nIdLst ); + m_rSt.ReadUInt32( aLST.nTplC ); + for (sal_uInt16 & nLevel : aLST.aIdSty) + m_rSt.ReadUInt16( nLevel ); + + sal_uInt8 aBits1(0); + m_rSt.ReadUChar( aBits1 ); + + m_rSt.SeekRel( 1 ); + + if( aBits1 & 0x01 ) + aLST.bSimpleList = true; + if( aBits1 & 0x02 ) + aLST.bRestartHdn = true; + + // 1.1.2 new NumRule inserted in Doc and WW8LSTInfo marked + + /* + #i1869# + In word 2000 microsoft got rid of creating new "simple lists" with + only 1 level, all new lists are created with 9 levels. To hack it + so that the list types formerly known as simple lists still have + their own tab page to themselves one of the reserved bits is used + to show that a given list is to be in the simple list tabpage. + This has now nothing to do with the actual number of list level a + list has, only how many will be shown in the user interface. + + i.e. create a simple list in 2000 and open it in 97 and 97 will + claim (correctly) that it is an outline list. We can set our + continuous flag in these lists to store this information. + */ + SwNumRule* pMyNumRule = CreateNextRule( + aLST.bSimpleList || (aBits1 & 0x10)); + + WW8LSTInfo* pLSTInfo = new WW8LSTInfo(pMyNumRule, aLST); + maLSTInfos.emplace_back(pLSTInfo); + + nRemainingPlcfLst -= cbLSTF; + } + + // 1.2 read all LVL of all aLST + + sal_uInt16 nLSTInfos = static_cast< sal_uInt16 >(maLSTInfos.size()); + for (sal_uInt16 nList = 0; nList < nLSTInfos; ++nList) + { + WW8aISet aItemSet; // Character attributes from GrpprlChpx + + WW8LSTInfo* pListInfo = maLSTInfos[nList].get(); + if( !pListInfo || !pListInfo->pNumRule ) break; + SwNumRule& rMyNumRule = *pListInfo->pNumRule; + + // 1.2.1 read specific LVL(s) for this aLST + + sal_uInt16 nLvlCount = static_cast< sal_uInt16 >(pListInfo->bSimpleList ? nMinLevel : nMaxLevel); + pListInfo->maParaSprms.resize(nMaxLevel); + for (sal_uInt16 nLevel = 0; nLevel < nLvlCount; ++nLevel) + { + SwNumFormat aNumFormat( rMyNumRule.Get( nLevel ) ); + // read LVLF + bLVLOk = ReadLVL( aNumFormat, aItemSet[nLevel], + pListInfo->aIdSty[nLevel], true, nLevel, + pListInfo->maParaSprms[nLevel]); + if( !bLVLOk ) + break; + // and set in rMyNumRule + rMyNumRule.Set( nLevel, aNumFormat ); + } + if( !bLVLOk ) + break; + + // 1.2.2 compare ItemPools and CHPx Settings of different Levels + // and create Style(s) if necessary + + for (sal_uInt16 nLevel = 0; nLevel < nLvlCount; ++nLevel) + { + bool bDummy; + AdjustLVL( nLevel, rMyNumRule, aItemSet, + pListInfo->aCharFormat, bDummy ); + } + } + + // 2. read and save PLF LFO + + bOk = checkSeek(m_rSt, m_rFib.m_fcPlfLfo); + + if (!bOk) + return; + + sal_Int32 nLfoCount(0); + m_rSt.ReadInt32( nLfoCount ); + bOk = nLfoCount > 0; + + if (!bOk) + return; + + // 2.1 read all LFO + + for (sal_Int32 nLfo = 0; nLfo < nLfoCount; ++nLfo) + { + bOk = false; + + WW8LFO aLFO = {}; + + m_rSt.ReadUInt32( aLFO.nIdLst ); + m_rSt.SeekRel( 8 ); + m_rSt.ReadUChar( aLFO.nLfoLvl ); + if (!m_rSt.good()) + break; + m_rSt.SeekRel( 3 ); + // as many Overrides as there are + if ((nMaxLevel < aLFO.nLfoLvl) || m_rSt.GetError()) + break; + + // get the Parent NumRule of the current List + WW8LSTInfo* pParentListInfo = GetLSTByListId(aLFO.nIdLst); + if (pParentListInfo) + { + // Save the NumRule in this first step + aLFO.pNumRule = pParentListInfo->pNumRule; + + // are there multiple Levels in the List? + aLFO.bSimpleList = pParentListInfo->bSimpleList; + } + // store in Array + std::unique_ptr<WW8LFOInfo> pLFOInfo(new WW8LFOInfo(aLFO)); + if (pParentListInfo) + { + //Copy the basic paragraph properties for each level from the + //original list into the list format override levels. + int nMaxSize = pParentListInfo->maParaSprms.size(); + pLFOInfo->maParaSprms.resize(nMaxSize); + for (int i = 0; i < nMaxSize; ++i) + pLFOInfo->maParaSprms[i] = pParentListInfo->maParaSprms[i]; + } + m_LFOInfos.push_back(std::move(pLFOInfo)); + bOk = true; + } + + if( bOk ) + { + + // 2.2 read specific LFOLVL for all LFO + + size_t nLFOInfos = m_LFOInfos.size(); + for (size_t nLfo = 0; nLfo < nLFOInfos; ++nLfo) + { + WW8LFOInfo& rLFOInfo = *m_LFOInfos[nLfo]; + // Do LFOLVL exist? + if( rLFOInfo.bOverride ) + { + WW8LSTInfo* pParentListInfo = GetLSTByListId(rLFOInfo.nIdLst); + if (!pParentListInfo) + break; + + // 2.2.1 create new NumRule for this List + + SwNumRule* pParentNumRule = rLFOInfo.pNumRule; + OSL_ENSURE(pParentNumRule, "ww: Impossible lists, please report"); + if( !pParentNumRule ) + break; + // create name-prefix for NumRule-Name + // and (if necessary) for Style-Name + const OUString sPrefix("WW8NumSt" + OUString::number( nLfo + 1 )); + // Now assign pNumRule its actual value!!! + // (it contained the parent NumRule up to this point) + + // check if a Style is referencing this LFO + if( USHRT_MAX > m_rReader.StyleUsingLFO( nLfo ) ) + { + sal_uInt16 nRul = m_rDoc.MakeNumRule( + m_rDoc.GetUniqueNumRuleName( &sPrefix ), pParentNumRule); + rLFOInfo.pNumRule = m_rDoc.GetNumRuleTable()[ nRul ]; + rLFOInfo.pNumRule->SetAutoRule(false); + } + else + { + sal_uInt16 nRul = m_rDoc.MakeNumRule( + m_rDoc.GetUniqueNumRuleName(), pParentNumRule); + rLFOInfo.pNumRule = m_rDoc.GetNumRuleTable()[ nRul ]; + rLFOInfo.pNumRule->SetAutoRule(true); // = default + } + + // 2.2.2 read all LFOLVL (and LVL) for the new NumRule + + WW8aISet aItemSet; // Character attributes from GrpprlChpx + WW8aCFormat aCharFormat = {}; // Character Style Pointer + + //2.2.2.0 skip inter-group of override header ? + //See #i25438# for why I moved this here, compare + //that original bugdoc's binary to what it looks like + //when resaved with word, i.e. there is always a + //4 byte header, there might be more than one if + //that header was 0xFFFFFFFF, e.g. #114412# ? + sal_uInt32 nTest; + m_rSt.ReadUInt32( nTest ); + do + { + nTest = 0; + m_rSt.ReadUInt32( nTest ); + } + while (nTest == 0xFFFFFFFF); + m_rSt.SeekRel(-4); + + for (sal_uInt8 nLevel = 0; nLevel < rLFOInfo.nLfoLvl; ++nLevel) + { + WW8LFOLVL aLFOLVL; + bLVLOk = false; + + // 2.2.2.1 read LFOLVL + + m_rSt.ReadInt32( aLFOLVL.nStartAt ); + sal_uInt8 aBits1(0); + m_rSt.ReadUChar( aBits1 ); + m_rSt.SeekRel( 3 ); + if (m_rSt.GetError()) + break; + + // Note: MS writes the Override-Level-Number into 4 bit. + // We do not! (See comment at "struct WW8LFOInfo") + aLFOLVL.nLevel = aBits1 & 0x0F; + if( (0xFF > aBits1) && + (nMaxLevel > aLFOLVL.nLevel) ) + { + if (aBits1 & 0x10) + aLFOLVL.bStartAt = true; + else + aLFOLVL.bStartAt = false; + + // 2.2.2.2 load dedicated LVL if necessary + + SwNumFormat aNumFormat( + rLFOInfo.pNumRule->Get(aLFOLVL.nLevel)); + if (aBits1 & 0x20) + { + aLFOLVL.bFormat = true; + // if bStartup is true, replace Startup-Level + // with the LVLF that is saved in the LVL + bLVLOk = nLevel < rLFOInfo.maParaSprms.size() && + ReadLVL(aNumFormat, aItemSet[nLevel], + pParentListInfo->aIdSty[nLevel], + aLFOLVL.bStartAt, nLevel, + rLFOInfo.maParaSprms[nLevel]); + + if (!bLVLOk) + break; + } + else if (aLFOLVL.bStartAt) + { + aNumFormat.SetStart( + writer_cast<sal_uInt16>(aLFOLVL.nStartAt)); + } + + // 2.2.2.3 Set NumFormat in NumRule + + rLFOInfo.pNumRule->Set(aLFOLVL.nLevel, aNumFormat); + } + bLVLOk = true; + + if (nMaxLevel > aLFOLVL.nLevel) + rLFOInfo.maOverrides[aLFOLVL.nLevel] = aLFOLVL; + } + if( !bLVLOk ) + break; + + // 2.2.3 adjust LVL of the new NumRule + + bool bNewCharFormatCreated = false; + for (sal_uInt8 nLevel = 0; nLevel < rLFOInfo.nLfoLvl; ++nLevel) + { + AdjustLVL( nLevel, *rLFOInfo.pNumRule, aItemSet, aCharFormat, + bNewCharFormatCreated, sPrefix ); + } + } + } + } + // and we're done! + m_rSt.Seek( nOriginalPos ); +} + +void WW8ListManager::ImplDestroy() +{ + /* + named lists remain in document + unused automatic lists are removed from document (DelNumRule) + */ + for(auto & rpInfo : maLSTInfos) + { + if (rpInfo->pNumRule && !rpInfo->bUsedInDoc && + rpInfo->pNumRule->IsAutoRule()) + { + m_rDoc.DelNumRule(rpInfo->pNumRule->GetName()); + } + rpInfo.reset(); + } + for (auto aIter = m_LFOInfos.rbegin(); aIter != m_LFOInfos.rend(); ++aIter) + { + if ((*aIter)->bOverride + && (*aIter)->pNumRule + && !(*aIter)->bUsedInDoc + && (*aIter)->pNumRule->IsAutoRule()) + { + m_rDoc.DelNumRule( (*aIter)->pNumRule->GetName() ); + } + } +} + +WW8ListManager::~WW8ListManager() +{ + suppress_fun_call_w_exception(ImplDestroy()); +} + +static bool IsEqualFormatting(const SwNumRule &rOne, const SwNumRule &rTwo) +{ + bool bRet = + ( + rOne.GetRuleType() == rTwo.GetRuleType() && + rOne.IsContinusNum() == rTwo.IsContinusNum() && + rOne.IsAbsSpaces() == rTwo.IsAbsSpaces() && + rOne.GetPoolFormatId() == rTwo.GetPoolFormatId() && + rOne.GetPoolHelpId() == rTwo.GetPoolHelpId() && + rOne.GetPoolHlpFileId() == rTwo.GetPoolHlpFileId() + ); + + if (bRet) + { + for (sal_uInt8 n = 0; n < MAXLEVEL; ++n ) + { + //The SvxNumberFormat compare, not the SwNumFormat compare + const SvxNumberFormat &rO = rOne.Get(n); + const SvxNumberFormat &rT = rTwo.Get(n); + if (rO != rT) + { + bRet = false; + break; + } + } + } + return bRet; +} + +SwNumRule* WW8ListManager::GetNumRuleForActivation(sal_uInt16 nLFOPosition, + const sal_uInt8 nLevel, std::vector<sal_uInt8> &rParaSprms, SwTextNode *pNode) +{ + if (m_LFOInfos.size() <= nLFOPosition) + return nullptr; + + WW8LFOInfo& rLFOInfo = *m_LFOInfos[nLFOPosition]; + + bool bFirstUse = !rLFOInfo.bUsedInDoc; + rLFOInfo.bUsedInDoc = true; + + if( !rLFOInfo.pNumRule ) + return nullptr; + + // #i25545# + // #i100132# - a number format does not have to exist on given list level + SwNumFormat aFormat(rLFOInfo.pNumRule->Get(nLevel)); + + if (m_rReader.IsRightToLeft() && m_nLastLFOPosition != nLFOPosition) { + if ( aFormat.GetNumAdjust() == SvxAdjust::Right) + aFormat.SetNumAdjust(SvxAdjust::Left); + else if ( aFormat.GetNumAdjust() == SvxAdjust::Left) + aFormat.SetNumAdjust(SvxAdjust::Right); + rLFOInfo.pNumRule->Set(nLevel, aFormat); + } + m_nLastLFOPosition = nLFOPosition; + /* + #i1869# + If this list has had its bits set in word 2000 to pretend that it is a + simple list from the point of view of the user, then it is almost + certainly a simple continuous list, and we will try to keep it like that. + Otherwise when we save again it will be shown as the true outline list + that it is, confusing the user that just wanted what they thought was a + simple list. On the other hand it is possible that some of the other levels + were used by the user, in which case we will not pretend anymore that it + is a simple list. Something that word 2000 does anyway, that 97 didn't, to + my bewilderment. + */ + if (nLevel && rLFOInfo.pNumRule->IsContinusNum()) + rLFOInfo.pNumRule->SetContinusNum(false); + + if( (!rLFOInfo.bOverride) && (!rLFOInfo.bLSTbUIDSet) ) + { + WW8LSTInfo* pParentListInfo = GetLSTByListId( rLFOInfo.nIdLst ); + if( pParentListInfo ) + pParentListInfo->bUsedInDoc = true; + rLFOInfo.bLSTbUIDSet = true; + } + + if (rLFOInfo.maParaSprms.size() > nLevel) + rParaSprms = rLFOInfo.maParaSprms[nLevel]; + + SwNumRule *pRet = rLFOInfo.pNumRule; + + bool bRestart(false); + sal_uInt16 nStart(0); + bool bNewstart(false); + /* + Note: If you fiddle with this then you have to make sure that #i18322# + #i13833#, #i20095# and #112466# continue to work + + Check if there were overrides for this level + */ + if (rLFOInfo.bOverride && nLevel < rLFOInfo.nLfoLvl) + { + WW8LSTInfo* pParentListInfo = GetLSTByListId(rLFOInfo.nIdLst); + OSL_ENSURE(pParentListInfo, "ww: Impossible lists, please report"); + if (pParentListInfo && pParentListInfo->pNumRule) + { + const WW8LFOLVL &rOverride = rLFOInfo.maOverrides[nLevel]; + bool bNoChangeFromParent = + IsEqualFormatting(*pRet, *(pParentListInfo->pNumRule)); + + //If so then I think word still uses the parent (maybe) + if (bNoChangeFromParent) + { + pRet = pParentListInfo->pNumRule; + + //did it not affect start at value ? + if (bFirstUse && rOverride.bStartAt) + { + const SwNumFormat &rFormat = + pParentListInfo->pNumRule->Get(nLevel); + if ( + rFormat.GetStart() == + rLFOInfo.maOverrides[nLevel].nStartAt + ) + { + bRestart = true; + } + else + { + bNewstart = true; + nStart = writer_cast<sal_uInt16> + (rLFOInfo.maOverrides[nLevel].nStartAt); + } + } + + pParentListInfo->bUsedInDoc = true; + } + } + } + + if (pNode) + { + pNode->SetAttrListLevel(nLevel); + + if (bRestart || bNewstart) + pNode->SetListRestart(true); + if (bNewstart) + pNode->SetAttrListRestartValue(nStart); + } + return pRet; +} + +// SwWW8ImplReader: append a List to a Style or Paragraph + +bool SwWW8ImplReader::SetTextFormatCollAndListLevel(const SwPaM& rRg, + SwWW8StyInf& rStyleInfo) +{ + bool bRes = true; + if( rStyleInfo.m_pFormat && rStyleInfo.m_bColl ) + { + bRes = m_rDoc.SetTextFormatColl(rRg, static_cast<SwTextFormatColl*>(rStyleInfo.m_pFormat)); + SwTextNode* pTextNode = m_pPaM->GetPointNode().GetTextNode(); + OSL_ENSURE( pTextNode, "No Text-Node at PaM-Position" ); + if ( !pTextNode ) + { + // make code robust + return bRes; + } + + const SwNumRule * pNumRule = pTextNode->GetNumRule(); // #i27610# + + if( !IsInvalidOrToBeMergedTabCell() && + ! (pNumRule && pNumRule->IsOutlineRule()) ) // #i27610# + { + pTextNode->ResetAttr( RES_PARATR_NUMRULE ); + } + + if (USHRT_MAX > rStyleInfo.m_nLFOIndex && WW8ListManager::nMaxLevel + > rStyleInfo.m_nListLevel) + { + const bool bApplyListStyle = false; + RegisterNumFormatOnTextNode(rStyleInfo.m_nLFOIndex, rStyleInfo.m_nListLevel, + bApplyListStyle); + } + } + return bRes; +} + +void UseListIndent(SwWW8StyInf &rStyle, const SwNumFormat &rFormat) +{ + // #i86652# + if ( rFormat.GetPositionAndSpaceMode() == SvxNumberFormat::LABEL_WIDTH_AND_POSITION ) + { + const auto nAbsLSpace = rFormat.GetAbsLSpace(); + const tools::Long nListFirstLineIndent = GetListFirstLineIndent(rFormat); + SvxFirstLineIndentItem firstLine(rStyle.m_pFormat->GetFormatAttr(RES_MARGIN_FIRSTLINE)); + SvxTextLeftMarginItem leftMargin(rStyle.m_pFormat->GetFormatAttr(RES_MARGIN_TEXTLEFT)); + leftMargin.SetTextLeft(nAbsLSpace); + firstLine.SetTextFirstLineOffset(writer_cast<short>(nListFirstLineIndent)); + rStyle.m_pFormat->SetFormatAttr(firstLine); + rStyle.m_pFormat->SetFormatAttr(leftMargin); + rStyle.m_bListRelevantIndentSet = true; + } +} + +void SetStyleIndent(SwWW8StyInf &rStyle, const SwNumFormat &rFormat) +{ + if ( rFormat.GetPositionAndSpaceMode() != SvxNumberFormat::LABEL_WIDTH_AND_POSITION ) // #i86652# + return; + + SvxFirstLineIndentItem firstLine(rStyle.m_pFormat->GetFormatAttr(RES_MARGIN_FIRSTLINE)); + SvxTextLeftMarginItem leftMargin(rStyle.m_pFormat->GetFormatAttr(RES_MARGIN_TEXTLEFT)); + if (rStyle.m_bListRelevantIndentSet) + { + + SyncIndentWithList(firstLine, leftMargin, rFormat, false, false); // #i103711#, #i105414# + } + else + { + leftMargin.SetTextLeft(0); + firstLine.SetTextFirstLineOffset(0); + } + rStyle.m_pFormat->SetFormatAttr(firstLine); + rStyle.m_pFormat->SetFormatAttr(leftMargin); +} + +void SwWW8ImplReader::SetStylesList(sal_uInt16 nStyle, sal_uInt16 nCurrentLFO, + sal_uInt8 nCurrentLevel) +{ + if (nStyle >= m_vColl.size()) + return; + + SwWW8StyInf &rStyleInf = m_vColl[nStyle]; + if (!rStyleInf.m_bValid) + return; + + OSL_ENSURE(m_pCurrentColl, "Cannot be called outside of style import"); + // Phase 1: Numbering attributes when reading a StyleDef + if( !m_pCurrentColl ) + return; + + if (nCurrentLFO < USHRT_MAX) + rStyleInf.m_nLFOIndex = nCurrentLFO; + if (nCurrentLevel < MAXLEVEL) + rStyleInf.m_nListLevel = nCurrentLevel; + + // only save the Parameters for now. The actual List will be appended + // at a later point, when the Listdefinitions is read... + if (rStyleInf.m_nLFOIndex < USHRT_MAX && rStyleInf.m_nListLevel < WW8ListManager::nMaxLevel) + { + std::vector<sal_uInt8> aParaSprms; + SwNumRule* pNmRule = m_xLstManager->GetNumRuleForActivation( + rStyleInf.m_nLFOIndex, rStyleInf.m_nListLevel, aParaSprms); + if (pNmRule) + UseListIndent(rStyleInf, pNmRule->Get(rStyleInf.m_nListLevel)); + } +} + +void SwWW8ImplReader::RegisterNumFormatOnStyle(sal_uInt16 nStyle) +{ + + if (nStyle >= m_vColl.size()) + return; + + SwWW8StyInf &rStyleInf = m_vColl[nStyle]; + if (!(rStyleInf.m_bValid && rStyleInf.m_pFormat)) + return; + + //Save old pre-list modified indent, which are the word indent values + rStyleInf.m_pWordFirstLine.reset(rStyleInf.m_pFormat->GetFormatAttr(RES_MARGIN_FIRSTLINE).Clone()); + rStyleInf.m_pWordLeftMargin.reset(rStyleInf.m_pFormat->GetFormatAttr(RES_MARGIN_TEXTLEFT).Clone()); + rStyleInf.m_pWordRightMargin.reset(rStyleInf.m_pFormat->GetFormatAttr(RES_MARGIN_RIGHT).Clone()); + + // Phase 2: refresh StyleDef after reading all Lists + if (rStyleInf.m_nLFOIndex >= USHRT_MAX || rStyleInf.m_nListLevel >= WW8ListManager::nMaxLevel) + return; + + std::vector<sal_uInt8> aParaSprms; + SwNumRule* pNmRule = m_xLstManager->GetNumRuleForActivation( + rStyleInf.m_nLFOIndex, rStyleInf.m_nListLevel, aParaSprms); + + if (pNmRule != nullptr) + { + if (rStyleInf.IsWW8BuiltInHeadingStyle() + && rStyleInf.HasWW8OutlineLevel()) + { + rStyleInf.m_pOutlineNumrule = pNmRule; + } + else + { + rStyleInf.m_pFormat->SetFormatAttr( + SwNumRuleItem(pNmRule->GetName())); + rStyleInf.m_bHasStyNumRule = true; + } + + SetStyleIndent(rStyleInf, pNmRule->Get(rStyleInf.m_nListLevel)); + } +} + +void SwWW8ImplReader::RegisterNumFormatOnTextNode(sal_uInt16 nCurrentLFO, + sal_uInt8 nCurrentLevel, + const bool bSetAttr) +{ + // Note: the method appends NumRule to the Text Node if + // bSetAttr (of course the lists have to be read before) + // and only sets the Level. It does not check if there is a NumRule + // attached to the STYLE !!! + + if (!m_xLstManager) // are all list declarations read? + return; + + SwTextNode* pTextNd = m_pPaM->GetPointNode().GetTextNode(); + OSL_ENSURE(pTextNd, "No Text-Node at PaM-Position"); + if (!pTextNd) + return; + + // WW8ListManager::nMaxLevel indicates body text, cancelling an inherited numbering. + if (nCurrentLFO < USHRT_MAX && nCurrentLevel == WW8ListManager::nMaxLevel) + { + pTextNd->SetAttr(SwNumRuleItem(OUString())); + return; + } + + // Undefined listLevel is treated as the first level with valid numbering rule. + // TODO:This doesn't allow for inheriting from a style(HOW?), but it matches previous behaviour. + if (nCurrentLFO < USHRT_MAX && nCurrentLevel == MAXLEVEL) + nCurrentLevel = 0; + + std::vector<sal_uInt8> aParaSprms; + const SwNumRule* pRule = bSetAttr ? + m_xLstManager->GetNumRuleForActivation( nCurrentLFO, nCurrentLevel, + aParaSprms, pTextNd) : nullptr; + + if (pRule == nullptr && bSetAttr) + return; + + if (bSetAttr && pTextNd->GetNumRule() != pRule + && (pTextNd->GetNumRule() != m_rDoc.GetOutlineNumRule() + || pRule != m_pChosenWW8OutlineStyle)) + { + // Now this is either not a part of Chapter Numbering, + // or else it is using a different numRule than the one copied to Chapter Numbering. + OUString sName = pRule == m_pChosenWW8OutlineStyle ? m_rDoc.GetOutlineNumRule()->GetName() + : pRule->GetName(); + pTextNd->SetAttr(SwNumRuleItem(sName)); + } + pTextNd->SetAttrListLevel(nCurrentLevel); + + // <IsCounted()> state of text node has to be adjusted accordingly. + if ( /*nCurrentLevel >= 0 &&*/ nCurrentLevel < MAXLEVEL ) + { + pTextNd->SetCountedInList( true ); + } + + // #i99822# + // Direct application of the list level formatting no longer + // needed for list levels of mode LABEL_ALIGNMENT + bool bApplyListLevelIndentDirectlyAtPara(true); + { + if (pTextNd->GetNumRule() && nCurrentLevel < MAXLEVEL) + { + const SwNumFormat& rFormat = pTextNd->GetNumRule()->Get(nCurrentLevel); + if (rFormat.GetPositionAndSpaceMode() + == SvxNumberFormat::LABEL_ALIGNMENT) + { + bApplyListLevelIndentDirectlyAtPara = false; + } + } + } + + if (!bApplyListLevelIndentDirectlyAtPara) + return; + + auto pListIndent = std::make_unique<SfxItemSet>(m_rDoc.GetAttrPool(), svl::Items<RES_MARGIN_FIRSTLINE, RES_MARGIN_TEXTLEFT>); + const SfxPoolItem *pItem; + pItem = GetFormatAttr(RES_MARGIN_FIRSTLINE); + OSL_ENSURE(pItem, "impossible"); + if (pItem) + pListIndent->Put(*pItem); + pItem = GetFormatAttr(RES_MARGIN_TEXTLEFT); + if (pItem) + pListIndent->Put(*pItem); + + /* + Take the original paragraph sprms attached to this list level + formatting and apply them to the paragraph. I'm convinced that + this is exactly what word does. + */ + if (short nLen = static_cast< short >(aParaSprms.size())) + { + std::unique_ptr<SfxItemSet> pOldCurrentItemSet(SetCurrentItemSet(std::move(pListIndent))); + + sal_uInt8* pSprms1 = aParaSprms.data(); + while (0 < nLen) + { + sal_uInt16 nL1 = ImportSprm(pSprms1, nLen); + nLen = nLen - nL1; + pSprms1 += nL1; + } + + pListIndent = SetCurrentItemSet(std::move(pOldCurrentItemSet)); + } + + if (const SvxFirstLineIndentItem *const pFirstLine = pListIndent->GetItem<SvxFirstLineIndentItem>(RES_MARGIN_FIRSTLINE)) + { + m_xCtrlStck->NewAttr(*m_pPaM->GetPoint(), *pFirstLine); + m_xCtrlStck->SetAttr(*m_pPaM->GetPoint(), RES_MARGIN_FIRSTLINE); + } + if (const SvxTextLeftMarginItem *const pLeftMargin = pListIndent->GetItem<SvxTextLeftMarginItem>(RES_MARGIN_TEXTLEFT)) + { + m_xCtrlStck->NewAttr(*m_pPaM->GetPoint(), *pLeftMargin); + m_xCtrlStck->SetAttr(*m_pPaM->GetPoint(), RES_MARGIN_TEXTLEFT); + } +} + +void SwWW8ImplReader::RegisterNumFormat(sal_uInt16 nCurrentLFO, sal_uInt8 nCurrentLevel) +{ + // Are we reading the StyleDef ? + if (m_pCurrentColl) + SetStylesList( m_nCurrentColl , nCurrentLFO, nCurrentLevel); + else + RegisterNumFormatOnTextNode(nCurrentLFO, nCurrentLevel); +} + +void SwWW8ImplReader::Read_ListLevel(sal_uInt16, const sal_uInt8* pData, + short nLen) +{ + if (m_xPlcxMan && m_xPlcxMan->GetDoingDrawTextBox()) + return; + + if( nLen < 0 ) + { + // the current level is finished, what should we do ? + m_nListLevel = MAXLEVEL; + if (m_xStyles && !m_bVer67) + m_xStyles->mnWwNumLevel = 0; + } + else + { + // security check + if( !pData ) + return; + + // the Streamdata is zero based + m_nListLevel = *pData; + + if (m_xStyles && !m_bVer67) + { + /* + if this is the case, then if the numbering is actually stored in + winword 6 format, and its likely that sprmPIlvl has been abused + to set the ww6 list level information which we will need when we + reach the true ww6 list def. So set it now + */ + m_xStyles->mnWwNumLevel = m_nListLevel; + } + + // Treat an invalid level as body-level + if (WW8ListManager::nMaxLevel < m_nListLevel) + m_nListLevel = WW8ListManager::nMaxLevel; + + RegisterNumFormat(m_nLFOPosition, m_nListLevel); + if (USHRT_MAX > m_nLFOPosition) + { + assert(false && "m_nLFOPosition is usually reset immediately, so we rarely ever get here."); + m_nLFOPosition = USHRT_MAX; + m_nListLevel = MAXLEVEL; + } + } +} + +void SwWW8ImplReader::Read_LFOPosition(sal_uInt16, const sal_uInt8* pData, + short nLen) +{ + if (m_xPlcxMan && m_xPlcxMan->GetDoingDrawTextBox()) + return; + + if( nLen < 0 ) + { + // the current level is finished, what should we do ? + m_nLFOPosition = USHRT_MAX; + m_nListLevel = MAXLEVEL; + } + else + { + // security check + if( !pData ) + return; + + short nData = SVBT16ToUInt16( pData ); + if( 0 >= nData ) + { + // disable the numbering/list style apply to the paragraph or the style + + /* + If you have a paragraph in word with left and/or hanging indent + and remove its numbering, then the indentation appears to get + reset, but not back to the base style, instead it goes to a blank + setting. + Unless it's a broken ww6 list in 97 in which case more hackery is + required, some more details about broken ww6 list in + ww8par6.cxx#SwWW8ImplReader::Read_LR + */ + + if (m_pCurrentColl) + { + // here a "named" style is being configured + + // disable the numbering/list in the style currently configured + m_pCurrentColl->SetFormatAttr(*GetDfltAttr(RES_PARATR_NUMRULE)); + + // reset/blank the indent + m_pCurrentColl->SetFormatAttr(SvxFirstLineIndentItem(RES_MARGIN_FIRSTLINE)); + m_pCurrentColl->SetFormatAttr(SvxTextLeftMarginItem(RES_MARGIN_TEXTLEFT)); + m_pCurrentColl->SetFormatAttr(SvxRightMarginItem(RES_MARGIN_RIGHT)); + + // These sprmPIlfos are supposed to indicate "cancel" numbering. + // Since m_nLFOPosition is "data - 1", then zero becomes USHRT_MAX + // which is no good since that indicates "unspecified, available for inheritance". + // So instead use USHRT_MAX-1 for indicating an explicit "cancel numbering". + RegisterNumFormat(USHRT_MAX-1, MAXLEVEL); + } + else if (SwTextNode* pTextNode = m_pPaM->GetPointNode().GetTextNode()) + { + // here a paragraph is being directly formatted + + // empty the numbering/list style applied to the current paragraph + SwNumRuleItem aEmptyRule; + pTextNode->SetAttr( aEmptyRule ); + + // create an empty SvxLRSpaceItem + std::shared_ptr<SvxFirstLineIndentItem> pFirstLine(std::make_shared<SvxFirstLineIndentItem>(RES_MARGIN_FIRSTLINE)); + + // replace it with the one of the current node if it exist + if (const SvxFirstLineIndentItem * pItem = GetFormatAttr(RES_MARGIN_FIRSTLINE)) + pFirstLine.reset(pItem->Clone()); + + // reset/blank the left indent (and only the left) + pFirstLine->SetTextFirstLineOffset(0); + SvxTextLeftMarginItem leftMargin(0, RES_MARGIN_TEXTLEFT); + + // apply the modified SvxLRSpaceItem to the current paragraph + pTextNode->SetAttr(*pFirstLine); + pTextNode->SetAttr(leftMargin); + } + + m_nLFOPosition = USHRT_MAX; + } + else // nData in (0..0x7FFF] + { + m_nLFOPosition = o3tl::narrowing<sal_uInt16>(nData)-1; // m_nLFOPosition in [0..0x7FFF) + /* + If we are a ww8+ style with ww7- style lists then there is a + bizarre broken word bug where when the list is removed from a para + the ww6 list first line indent still affects the first line + indentation. Setting this flag will allow us to recover from this + braindeadness + */ + if (m_pCurrentColl && (m_nLFOPosition == 2047-1) && m_nCurrentColl < m_vColl.size()) + m_vColl[m_nCurrentColl].m_bHasBrokenWW6List = true; + + // here the stream data is 1-based, we subtract ONE + if (m_nLFOPosition != 2047-1) //Normal ww8+ list behaviour + { + RegisterNumFormat(m_nLFOPosition, m_nListLevel); + m_nLFOPosition = USHRT_MAX; + m_nListLevel = MAXLEVEL; + } + else if (m_xPlcxMan && m_xPlcxMan->HasParaSprm(NS_sprm::LN_PAnld).pSprm) + { + /* + #i8114# Horrific backwards compatible ww7- lists in ww8+ + docs + */ + m_nListLevel = std::min<sal_uInt8>(WW8ListManager::nMaxLevel, m_nListLevel); + Read_ANLevelNo(13 /*equiv ww7- sprm no*/, &m_nListLevel, 1); + } + } + } +} + +// Reading Controls + +bool SwWW8ImplReader::ImportFormulaControl(WW8FormulaControl &aFormula, + WW8_CP nStart, SwWw8ControlType nWhich ) +{ + bool bRet=false; + /* + * Save the reader state and process the sprms for this anchor cp. + * Doing so will set the nPicLocFc to the offset to find the hypertext + * data in the data stream. + */ + WW8_CP nEndCp = nStart+1; //Only interested in the single 0x01 character + + WW8ReaderSave aSave(this,nStart); + + WW8PLCFManResult aRes; + nStart = m_xPlcxMan->Where(); + while(nStart <= nEndCp) + { + if ( m_xPlcxMan->Get(&aRes) + && aRes.pMemPos && aRes.nSprmId ) + { + //only interested in sprms which would set nPicLocFc + if ( (68 == aRes.nSprmId) || (0x6A03 == aRes.nSprmId) ) + { + Read_PicLoc( aRes.nSprmId, aRes.pMemPos + + m_oSprmParser->DistanceToData(aRes.nSprmId), 4); + break; + } + } + m_xPlcxMan->advance(); + nStart = m_xPlcxMan->Where(); + } + sal_uLong nOffset = m_nPicLocFc; + aSave.Restore(this); + + sal_uInt64 nOldPos = m_pDataStream->Tell(); + WW8_PIC aPic; + bool bValid = checkSeek(*m_pDataStream, nOffset) && + PicRead(m_pDataStream, &aPic, m_bVer67); + + if (bValid && aPic.lcb > 0x3A) + { + aFormula.FormulaRead(nWhich,m_pDataStream); + bRet = true; + } + + /* + There is a problem with aPic, the WW8_PIC is always used even though it + is too big for the WW95 files, it needs to be modified to check the + version C. + */ + m_pDataStream->Seek( nOldPos ); + return bRet; +} + +void SwMSConvertControls::InsertFormula(WW8FormulaControl &rFormula) +{ + const uno::Reference< lang::XMultiServiceFactory > & rServiceFactory = + GetServiceFactory(); + + if(!rServiceFactory.is()) + return; + + awt::Size aSz; + uno::Reference< form::XFormComponent> xFComp; + + if (rFormula.Import(rServiceFactory, xFComp, aSz)) + { + uno::Reference <drawing::XShape> xShapeRef; + if (InsertControl(xFComp, aSz, &xShapeRef, false)) + GetShapes()->add(xShapeRef); + } +} + +void WW8FormulaControl::FormulaRead(SwWw8ControlType nWhich, + SvStream *pDataStream) +{ + sal_uInt8 nField; + + // The following is a FFData structure as described in + // Microsoft's DOC specification (chapter 2.9.78) + sal_uInt32 nVersion = 0; + pDataStream->ReadUInt32(nVersion); + // An unsigned integer that MUST be 0xFFFFFFFF + if (nVersion != 0xFFFFFFFF) + { + SAL_WARN("sw.ww8", "Parsing error: invalid header for FFData"); + return; // bail out + } + + // might be better to read the bits as a 16 bit word + // ( like it is in the spec. ) + sal_uInt8 bits1 = 0; + pDataStream->ReadUChar( bits1 ); + sal_uInt8 bits2 = 0; + pDataStream->ReadUChar( bits2 ); + + sal_uInt8 iType = ( bits1 & 0x3 ); + + // we should verify that bits.iType & nWhich concur + OSL_ENSURE( iType == nWhich, "something wrong, expect control type read from stream doesn't match nWhich passed in"); + if ( iType != nWhich ) + return; // bail out + + sal_uInt8 iRes = (bits1 & 0x7C) >> 2; + + pDataStream->ReadUInt16( mnMaxLen ); + + sal_uInt16 hps = 0; + pDataStream->ReadUInt16( hps ); + + // xstzName + msTitle = read_uInt16_BeltAndBracesString(*pDataStream); + + if (nWhich == WW8_CT_EDIT) + { // Field is a textbox + // Default text + // xstzTextDef + msDefault = read_uInt16_BeltAndBracesString(*pDataStream); + } + else + { + // CheckBox or ComboBox + sal_uInt16 wDef = 0; + pDataStream->ReadUInt16( wDef ); + mnChecked = wDef; // default + if (nWhich == WW8_CT_CHECKBOX) + { + if ( iRes != 25 ) + mnChecked = iRes; + msDefault = ( wDef == 0 ) ? std::u16string_view( u"0" ) : std::u16string_view( u"1" ); + } + } + // xstzTextFormat + msFormatting = read_uInt16_BeltAndBracesString(*pDataStream); + // xstzHelpText + msHelp = read_uInt16_BeltAndBracesString(*pDataStream); + // xstzStatText + msToolTip = read_uInt16_BeltAndBracesString(*pDataStream); + + // xstzEntryMcr + msEntryMcr = read_uInt16_BeltAndBracesString(*pDataStream); + //xstzExitMcr + msExitMcr = read_uInt16_BeltAndBracesString(*pDataStream); + + if (nWhich == WW8_CT_DROPDOWN) + { + bool bAllOk = true; + // SSTB (see Spec. 2.2.4) + sal_uInt16 fExtend = 0; + pDataStream->ReadUInt16( fExtend ); + sal_uInt16 nStringsCnt = 0; + + // Isn't it that if fExtend isn't 0xFFFF then fExtend actually + // doesn't exist and we really have just read nStringsCnt ( or cData )? + if (fExtend != 0xFFFF) + bAllOk = false; + pDataStream->ReadUInt16( nStringsCnt ); + + // I guess this should be zero ( and we should ensure that ) + sal_uInt16 cbExtra = 0; + pDataStream->ReadUInt16( cbExtra ); + + OSL_ENSURE(bAllOk, "Unknown formfield dropdown list structure"); + if (!bAllOk) //Not as expected, don't risk it at all. + nStringsCnt = 0; + const size_t nMinRecordSize = sizeof(sal_uInt16); + const size_t nMaxRecords = pDataStream->remainingSize() / nMinRecordSize; + if (nStringsCnt > nMaxRecords) + { + SAL_WARN("sw.ww8", "Parsing error: " << nMaxRecords << + " max possible entries, but " << nStringsCnt << " claimed, truncating"); + nStringsCnt = nMaxRecords; + } + maListEntries.reserve(nStringsCnt); + for (sal_uInt32 nI = 0; nI < nStringsCnt; ++nI) + { + OUString sEntry = read_uInt16_PascalString(*pDataStream); + maListEntries.push_back(sEntry); + } + } + mfDropdownIndex = iRes; + + mbHelp = bits1 & 0x80; + + nField = bits2; + mfToolTip = nField & 0x01; + mfNoMark = (nField & 0x02)>>1; + mfType = (nField & 0x38)>>3; + mfUnused = (nField & 0xE0)>>5; +} + +WW8FormulaListBox::WW8FormulaListBox(SwWW8ImplReader &rR) + : WW8FormulaControl(SL::aListBox, rR) +{ +} + +//Miserable hack to get a hardcoded guesstimate of the size of a list dropdown +//box's first entry to set as the lists default size +awt::Size SwWW8ImplReader::MiserableDropDownFormHack(const OUString &rString, + uno::Reference<beans::XPropertySet> const & rPropSet) +{ + awt::Size aRet; + struct CtrlFontMapEntry + { + sal_uInt16 nWhichId; + const char* pPropNm; + }; + const CtrlFontMapEntry aMapTable[] = + { + { RES_CHRATR_COLOR, "TextColor" }, + { RES_CHRATR_FONT, "FontName" }, + { RES_CHRATR_FONTSIZE, "FontHeight" }, + { RES_CHRATR_WEIGHT, "FontWeight" }, + { RES_CHRATR_UNDERLINE, "FontUnderline" }, + { RES_CHRATR_CROSSEDOUT, "FontStrikeout" }, + { RES_CHRATR_POSTURE, "FontSlant" }, + { 0, nullptr } + }; + + vcl::Font aFont; + uno::Reference< beans::XPropertySetInfo > xPropSetInfo = + rPropSet->getPropertySetInfo(); + + uno::Any aTmp; + for (const CtrlFontMapEntry* pMap = aMapTable; pMap->nWhichId; ++pMap) + { + bool bSet = true; + const SfxPoolItem* pItem = GetFormatAttr( pMap->nWhichId ); + OSL_ENSURE(pItem, "Impossible"); + if (!pItem) + continue; + + switch ( pMap->nWhichId ) + { + case RES_CHRATR_COLOR: + { + OUString aNm; + if (xPropSetInfo->hasPropertyByName(aNm = "TextColor")) + { + aTmp <<= static_cast<sal_Int32>(static_cast<const SvxColorItem*>(pItem)->GetValue()); + rPropSet->setPropertyValue(aNm, aTmp); + } + } + aFont.SetColor(static_cast<const SvxColorItem*>(pItem)->GetValue()); + break; + case RES_CHRATR_FONT: + { + const SvxFontItem *pFontItem = static_cast<const SvxFontItem *>(pItem); + OUString aNm; + if (xPropSetInfo->hasPropertyByName(aNm = "FontStyleName")) + { + aTmp <<= pFontItem->GetStyleName(); + rPropSet->setPropertyValue( aNm, aTmp ); + } + if (xPropSetInfo->hasPropertyByName(aNm = "FontFamily")) + { + aTmp <<= static_cast<sal_Int16>(pFontItem->GetFamily()); + rPropSet->setPropertyValue( aNm, aTmp ); + } + if (xPropSetInfo->hasPropertyByName(aNm = "FontCharset")) + { + aTmp <<= static_cast<sal_Int16>(pFontItem->GetCharSet()); + rPropSet->setPropertyValue( aNm, aTmp ); + } + if (xPropSetInfo->hasPropertyByName(aNm = "FontPitch")) + { + aTmp <<= static_cast<sal_Int16>(pFontItem->GetPitch()); + rPropSet->setPropertyValue( aNm, aTmp ); + } + + aTmp <<= pFontItem->GetFamilyName(); + aFont.SetFamilyName( pFontItem->GetFamilyName() ); + aFont.SetStyleName( pFontItem->GetStyleName() ); + aFont.SetFamily( pFontItem->GetFamily() ); + aFont.SetCharSet( pFontItem->GetCharSet() ); + aFont.SetPitch( pFontItem->GetPitch() ); + } + break; + + case RES_CHRATR_FONTSIZE: + { + Size aSize( aFont.GetFontSize().Width(), + static_cast<const SvxFontHeightItem*>(pItem)->GetHeight() ); + aTmp <<= static_cast<float>(aSize.Height()) / 20.0; + + aFont.SetFontSize(o3tl::convert(aSize, o3tl::Length::twip, o3tl::Length::mm100)); + } + break; + + case RES_CHRATR_WEIGHT: + aTmp <<= vcl::unohelper::ConvertFontWeight( + static_cast<const SvxWeightItem*>(pItem)->GetWeight() ); + aFont.SetWeight( static_cast<const SvxWeightItem*>(pItem)->GetWeight() ); + break; + + case RES_CHRATR_UNDERLINE: + aTmp <<= static_cast<sal_Int16>(static_cast<const SvxUnderlineItem*>(pItem)->GetLineStyle()); + aFont.SetUnderline(static_cast<const SvxUnderlineItem*>(pItem)->GetLineStyle()); + break; + + case RES_CHRATR_CROSSEDOUT: + aTmp <<= static_cast<sal_Int16>( static_cast<const SvxCrossedOutItem*>(pItem)->GetStrikeout() ); + aFont.SetStrikeout( static_cast<const SvxCrossedOutItem*>(pItem)->GetStrikeout() ); + break; + + case RES_CHRATR_POSTURE: + aTmp <<= static_cast<sal_Int16>( static_cast<const SvxPostureItem*>(pItem)->GetPosture() ); + aFont.SetItalic( static_cast<const SvxPostureItem*>(pItem)->GetPosture() ); + break; + + default: + bSet = false; + break; + } + + if (bSet && xPropSetInfo->hasPropertyByName(OUString::createFromAscii(pMap->pPropNm))) + rPropSet->setPropertyValue(OUString::createFromAscii(pMap->pPropNm), aTmp); + } + // now calculate the size of the control + OutputDevice* pOut = Application::GetDefaultDevice(); + OSL_ENSURE(pOut, "Impossible"); + if (pOut) + { + pOut->Push( vcl::PushFlags::FONT | vcl::PushFlags::MAPMODE ); + pOut->SetMapMode( MapMode( MapUnit::Map100thMM )); + pOut->SetFont( aFont ); + aRet.Width = pOut->GetTextWidth(rString); + aRet.Width += 500; //plus size of button, total hack territory + aRet.Height = pOut->GetTextHeight(); + pOut->Pop(); + } + return aRet; +} + +bool WW8FormulaListBox::Import(const uno::Reference < + lang::XMultiServiceFactory> &rServiceFactory, + uno::Reference <form::XFormComponent> &rFComp,awt::Size &rSz ) +{ + uno::Reference<uno::XInterface> xCreate = rServiceFactory->createInstance("com.sun.star.form.component.ComboBox"); + if( !xCreate.is() ) + return false; + + rFComp.set(xCreate, uno::UNO_QUERY); + if( !rFComp.is() ) + return false; + + uno::Reference<beans::XPropertySet> xPropSet(xCreate, uno::UNO_QUERY); + + uno::Any aTmp; + if (!msTitle.isEmpty()) + aTmp <<= msTitle; + else + aTmp <<= msName; + xPropSet->setPropertyValue("Name", aTmp ); + + if (!msToolTip.isEmpty()) + { + aTmp <<= msToolTip; + xPropSet->setPropertyValue("HelpText", aTmp ); + } + + xPropSet->setPropertyValue("Dropdown", css::uno::Any(true)); + + if (!maListEntries.empty()) + { + sal_uInt32 nLen = maListEntries.size(); + uno::Sequence< OUString > aListSource(nLen); + auto aListSourceRange = asNonConstRange(aListSource); + for (sal_uInt32 nI = 0; nI < nLen; ++nI) + aListSourceRange[nI] = maListEntries[nI]; + aTmp <<= aListSource; + xPropSet->setPropertyValue("StringItemList", aTmp ); + + if (mfDropdownIndex < nLen) + { + aTmp <<= aListSource[mfDropdownIndex]; + } + else + { + aTmp <<= aListSource[0]; + } + + xPropSet->setPropertyValue("DefaultText", aTmp ); + + rSz = mrRdr.MiserableDropDownFormHack(maListEntries[0], xPropSet); + } + else + { + static constexpr OUStringLiteral aBlank = + u"\u2002\u2002\u2002\u2002\u2002"; + rSz = mrRdr.MiserableDropDownFormHack(aBlank, xPropSet); + } + + return true; +} + +WW8FormulaCheckBox::WW8FormulaCheckBox(SwWW8ImplReader &rR) + : WW8FormulaControl(SL::aCheckBox, rR) +{ +} + +static void lcl_AddToPropertyContainer +(uno::Reference<beans::XPropertySet> const & xPropSet, + const OUString & rPropertyName, const OUString & rValue) +{ + uno::Reference<beans::XPropertySetInfo> xPropSetInfo = + xPropSet->getPropertySetInfo(); + if (xPropSetInfo.is() && + ! xPropSetInfo->hasPropertyByName(rPropertyName)) + { + uno::Reference<beans::XPropertyContainer> + xPropContainer(xPropSet, uno::UNO_QUERY); + uno::Any aAny((OUString())); + xPropContainer->addProperty + (rPropertyName, + static_cast<sal_Int16>(beans::PropertyAttribute::BOUND | + beans::PropertyAttribute::REMOVABLE), + aAny); + } + + uno::Any aAnyValue(rValue); + xPropSet->setPropertyValue(rPropertyName, aAnyValue ); +} + +bool WW8FormulaCheckBox::Import(const uno::Reference < + lang::XMultiServiceFactory> &rServiceFactory, + uno::Reference <form::XFormComponent> &rFComp,awt::Size &rSz ) +{ + uno::Reference< uno::XInterface > xCreate = rServiceFactory->createInstance("com.sun.star.form.component.CheckBox"); + if( !xCreate.is() ) + return false; + + rFComp.set( xCreate, uno::UNO_QUERY ); + if( !rFComp.is() ) + return false; + + uno::Reference< beans::XPropertySet > xPropSet( xCreate, uno::UNO_QUERY ); + + rSz.Width = 16 * mhpsCheckBox; + rSz.Height = 16 * mhpsCheckBox; + + uno::Any aTmp; + if (!msTitle.isEmpty()) + aTmp <<= msTitle; + else + aTmp <<= msName; + xPropSet->setPropertyValue("Name", aTmp ); + + aTmp <<= static_cast<sal_Int16>(mnChecked); + xPropSet->setPropertyValue("DefaultState", aTmp); + + if (!msToolTip.isEmpty()) + lcl_AddToPropertyContainer(xPropSet, "HelpText", msToolTip); + + if (!msHelp.isEmpty()) + lcl_AddToPropertyContainer(xPropSet, "HelpF1Text", msHelp); + + return true; + +} + +WW8FormulaEditBox::WW8FormulaEditBox(SwWW8ImplReader &rR) + : WW8FormulaControl(SL::aTextField ,rR) +{ +} + +bool SwMSConvertControls::InsertControl( + const uno::Reference< form::XFormComponent > & rFComp, + const awt::Size& rSize, uno::Reference< drawing::XShape > *pShape, + bool bFloatingCtrl) +{ + const uno::Reference< container::XIndexContainer > &rComps = GetFormComps(); + uno::Any aTmp( &rFComp, cppu::UnoType<form::XFormComponent>::get()); + rComps->insertByIndex( rComps->getCount(), aTmp ); + + const uno::Reference< lang::XMultiServiceFactory > &rServiceFactory = + GetServiceFactory(); + if( !rServiceFactory.is() ) + return false; + + uno::Reference< uno::XInterface > xCreate = rServiceFactory->createInstance( + "com.sun.star.drawing.ControlShape"); + if( !xCreate.is() ) + return false; + + uno::Reference< drawing::XShape > xShape(xCreate, uno::UNO_QUERY); + + OSL_ENSURE(xShape.is(), "Did not get XShape"); + xShape->setSize(rSize); + + uno::Reference< beans::XPropertySet > xShapePropSet( + xCreate, uno::UNO_QUERY ); + + //I lay a small bet that this will change to + //sal_Int16 nTemp=TextContentAnchorType::AS_CHARACTER; + text::TextContentAnchorType nTemp; + if (bFloatingCtrl) + nTemp = text::TextContentAnchorType_AT_PARAGRAPH; + else + nTemp = text::TextContentAnchorType_AS_CHARACTER; + + xShapePropSet->setPropertyValue("AnchorType", uno::Any(static_cast<sal_Int16>(nTemp)) ); + + xShapePropSet->setPropertyValue("VertOrient", uno::Any(sal_Int16(text::VertOrientation::TOP)) ); + + uno::Reference< text::XText > xDummyTextRef; + uno::Reference< text::XTextRange > xTextRg = + new SwXTextRange( *m_pPaM, xDummyTextRef ); + + aTmp <<= xTextRg; + xShapePropSet->setPropertyValue("TextRange", aTmp ); + + // Set the Control-Model for the Control-Shape + uno::Reference< drawing::XControlShape > xControlShape( xShape, + uno::UNO_QUERY ); + uno::Reference< awt::XControlModel > xControlModel( rFComp, + uno::UNO_QUERY ); + xControlShape->setControl( xControlModel ); + + if (pShape) + *pShape = xShape; + + return true; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ |