/* -*- 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 <editeng/formatbreakitem.hxx>
#include <sal/log.hxx>
#include <osl/diagnose.h>
#include <tools/stream.hxx>
#include <doc.hxx>
#include <IDocumentStatistics.hxx>
#include <IDocumentLayoutAccess.hxx>
#include <docstat.hxx>
#include <fmtpdsc.hxx>
#include <laycache.hxx>
#include "layhelp.hxx"
#include <pagefrm.hxx>
#include <rootfrm.hxx>
#include <txtfrm.hxx>
#include <swtable.hxx>
#include <tabfrm.hxx>
#include <rowfrm.hxx>
#include <sectfrm.hxx>
#include <fmtcntnt.hxx>
#include <pagedesc.hxx>
#include <frmtool.hxx>
#include <dflyobj.hxx>
#include <dcontact.hxx>
#include <viewopt.hxx>
#include <flyfrm.hxx>
#include <sortedobjs.hxx>
#include <ndindex.hxx>
#include <node.hxx>
#include <ndtxt.hxx>
#include <frameformats.hxx>
#include <limits>
using namespace ::com::sun::star;
SwLayoutCache::SwLayoutCache() : m_nLockCount( 0 ) {}
/*
* Reading and writing of the layout cache.
* The layout cache is not necessary, but it improves
* the performance and reduces the text flow during
* the formatting.
* The layout cache contains the index of the paragraphs/tables
* at the top of every page, so it's possible to create
* the right count of pages and to distribute the document content
* to this pages before the formatting starts.
*/
void SwLayoutCache::Read( SvStream &rStream )
{
if( !m_pImpl )
{
m_pImpl.reset( new SwLayCacheImpl );
if( !m_pImpl->Read( rStream ) )
{
m_pImpl.reset();
}
}
}
void SwLayCacheImpl::Insert( sal_uInt16 nType, SwNodeOffset nIndex, sal_Int32 nOffset )
{
m_aType.push_back( nType );
mIndices.push_back( nIndex );
m_aOffset.push_back( nOffset );
}
bool SwLayCacheImpl::Read( SvStream& rStream )
{
SwLayCacheIoImpl aIo( rStream, false );
if( aIo.GetMajorVersion() > SW_LAYCACHE_IO_VERSION_MAJOR )
return false;
// Due to an evil bug in the layout cache (#102759#), we cannot trust the
// sizes of fly frames which have been written using the "old" layout cache.
// This flag should indicate that we do not want to trust the width and
// height of fly frames
m_bUseFlyCache = aIo.GetMinorVersion() >= 1;
aIo.OpenRec( SW_LAYCACHE_IO_REC_PAGES );
aIo.OpenFlagRec();
aIo.CloseFlagRec();
while( aIo.BytesLeft() && !aIo.HasError() )
{
sal_uInt32 nIndex(0), nOffset(0);
switch( aIo.Peek() )
{
case SW_LAYCACHE_IO_REC_PARA:
{
aIo.OpenRec( SW_LAYCACHE_IO_REC_PARA );
sal_uInt8 cFlags = aIo.OpenFlagRec();
aIo.GetStream().ReadUInt32( nIndex );
if( (cFlags & 0x01) != 0 )
aIo.GetStream().ReadUInt32( nOffset );
else
nOffset = COMPLETE_STRING;
aIo.CloseFlagRec();
Insert( SW_LAYCACHE_IO_REC_PARA, SwNodeOffset(nIndex), static_cast<sal_Int32>(nOffset) );
aIo.CloseRec();
break;
}
case SW_LAYCACHE_IO_REC_TABLE:
aIo.OpenRec( SW_LAYCACHE_IO_REC_TABLE );
aIo.OpenFlagRec();
aIo.GetStream().ReadUInt32( nIndex )
.ReadUInt32( nOffset );
Insert( SW_LAYCACHE_IO_REC_TABLE, SwNodeOffset(nIndex), static_cast<sal_Int32>(nOffset) );
aIo.CloseFlagRec();
aIo.CloseRec();
break;
case SW_LAYCACHE_IO_REC_FLY:
{
aIo.OpenRec( SW_LAYCACHE_IO_REC_FLY );
aIo.OpenFlagRec();
aIo.CloseFlagRec();
sal_Int32 nX(0), nY(0), nW(0), nH(0);
sal_uInt16 nPgNum(0);
aIo.GetStream().ReadUInt16( nPgNum ).ReadUInt32( nIndex )
.ReadInt32( nX ).ReadInt32( nY ).ReadInt32( nW ).ReadInt32( nH );
m_FlyCache.emplace_back( nPgNum, nIndex, nX, nY, nW, nH );
aIo.CloseRec();
break;
}
default:
aIo.SkipRec();
break;
}
}
aIo.CloseRec();
return !aIo.HasError();
}
/** writes the index (more precise: the difference between
* the index and the first index of the document content)
* of the first paragraph/table at the top of every page.
* If at the top of a page is the rest of a paragraph/table
* from the bottom of the previous page, the character/row
* number is stored, too.
* The position, size and page number of the text frames
* are stored, too
*/
void SwLayoutCache::Write( SvStream &rStream, const SwDoc& rDoc )
{
if( !rDoc.getIDocumentLayoutAccess().GetCurrentLayout() ) // the layout itself ..
return;
SwLayCacheIoImpl aIo( rStream, true );
// We want to save the relative index, so we need the index
// of the first content
SwNodeOffset nStartOfContent = rDoc.GetNodes().GetEndOfContent().
StartOfSectionNode()->GetIndex();
// The first page...
SwPageFrame* pPage = const_cast<SwPageFrame*>(static_cast<const SwPageFrame*>(rDoc.getIDocumentLayoutAccess().GetCurrentLayout()->Lower()));
aIo.OpenRec( SW_LAYCACHE_IO_REC_PAGES );
aIo.OpenFlagRec( 0, 0 );
aIo.CloseFlagRec();
while( pPage )
{
if( pPage->GetPrev() )
{
SwLayoutFrame* pLay = pPage->FindBodyCont();
SwFrame* pTmp = pLay ? pLay->ContainsAny() : nullptr;
// We are only interested in paragraph or table frames,
// a section frames contains paragraphs/tables.
if( pTmp && pTmp->IsSctFrame() )
pTmp = static_cast<SwSectionFrame*>(pTmp)->ContainsAny();
if( pTmp ) // any content
{
if( pTmp->IsTextFrame() )
{
SwTextFrame const*const pFrame(static_cast<SwTextFrame const*>(pTmp));
assert(!pFrame->GetMergedPara());
SwNodeOffset nNdIdx = pFrame->GetTextNodeFirst()->GetIndex();
if( nNdIdx > nStartOfContent )
{
/* Open Paragraph Record */
aIo.OpenRec( SW_LAYCACHE_IO_REC_PARA );
bool bFollow = static_cast<SwTextFrame*>(pTmp)->IsFollow();
aIo.OpenFlagRec( bFollow ? 0x01 : 0x00,
bFollow ? 8 : 4 );
nNdIdx -= nStartOfContent;
aIo.GetStream().WriteUInt32( sal_Int32(nNdIdx) );
if( bFollow )
aIo.GetStream().WriteUInt32( sal_Int32(static_cast<SwTextFrame*>(pTmp)->GetOffset()) );
aIo.CloseFlagRec();
/* Close Paragraph Record */
aIo.CloseRec();
}
}
else if( pTmp->IsTabFrame() )
{
SwTabFrame* pTab = static_cast<SwTabFrame*>(pTmp);
assert(pTab);
sal_uLong nOfst = COMPLETE_STRING;
if( pTab->IsFollow() )
{
// If the table is a follow, we have to look for the
// master and to count all rows to get the row number
nOfst = 0;
if( pTab->IsFollow() )
pTab = pTab->FindMaster( true );
while( pTab != pTmp )
{
SwFrame* pSub = pTab->Lower();
while( pSub )
{
++nOfst;
pSub = pSub->GetNext();
}
pTab = pTab->GetFollow();
assert(pTab && "Table follow without master");
}
}
while (true)
{
SwNodeOffset nNdIdx =
pTab->GetTable()->GetTableNode()->GetIndex();
if( nNdIdx > nStartOfContent )
{
/* Open Table Record */
aIo.OpenRec( SW_LAYCACHE_IO_REC_TABLE );
aIo.OpenFlagRec( 0, 8 );
nNdIdx -= nStartOfContent;
aIo.GetStream().WriteUInt32( sal_Int32(nNdIdx) )
.WriteUInt32( nOfst );
aIo.CloseFlagRec();
/* Close Table Record */
aIo.CloseRec();
}
// If the table has a follow on the next page,
// we know already the row number and store this
// immediately.
if( pTab->GetFollow() )
{
if( nOfst == sal_uLong(COMPLETE_STRING) )
nOfst = 0;
do
{
SwFrame* pSub = pTab->Lower();
while( pSub )
{
++nOfst;
pSub = pSub->GetNext();
}
pTab = pTab->GetFollow();
SwPageFrame *pTabPage = pTab->FindPageFrame();
if( pTabPage != pPage )
{
OSL_ENSURE( pPage->GetPhyPageNum() <
pTabPage->GetPhyPageNum(),
"Looping Tableframes" );
pPage = pTabPage;
break;
}
} while ( pTab->GetFollow() );
}
else
break;
}
}
}
}
if( pPage->GetSortedObjs() )
{
SwSortedObjs &rObjs = *pPage->GetSortedObjs();
for (SwAnchoredObject* pAnchoredObj : rObjs)
{
if (SwFlyFrame *pFly = pAnchoredObj->DynCastFlyFrame())
{
if( pFly->getFrameArea().Left() != FAR_AWAY &&
!pFly->GetAnchorFrame()->FindFooterOrHeader() )
{
const SwContact *pC =
::GetUserCall(pAnchoredObj->GetDrawObj());
if( pC )
{
sal_uInt32 nOrdNum = pAnchoredObj->GetDrawObj()->GetOrdNum();
sal_uInt16 nPageNum = pPage->GetPhyPageNum();
/* Open Fly Record */
aIo.OpenRec( SW_LAYCACHE_IO_REC_FLY );
aIo.OpenFlagRec( 0, 0 );
aIo.CloseFlagRec();
const SwRect& rRct = pFly->getFrameArea();
sal_Int32 nX = rRct.Left() - pPage->getFrameArea().Left();
sal_Int32 nY = rRct.Top() - pPage->getFrameArea().Top();
aIo.GetStream().WriteUInt16( nPageNum ).WriteUInt32( nOrdNum )
.WriteInt32( nX ).WriteInt32( nY )
.WriteInt32( rRct.Width() )
.WriteInt32( rRct.Height() );
/* Close Fly Record */
aIo.CloseRec();
}
}
}
}
}
pPage = static_cast<SwPageFrame*>(pPage->GetNext());
}
aIo.CloseRec();
}
#ifdef DBG_UTIL
bool SwLayoutCache::CompareLayout( const SwDoc& rDoc ) const
{
if( !m_pImpl )
return true;
const SwRootFrame *pRootFrame = rDoc.getIDocumentLayoutAccess().GetCurrentLayout();
if( pRootFrame )
{
size_t nIndex = 0;
SwNodeOffset nStartOfContent = rDoc.GetNodes().GetEndOfContent().
StartOfSectionNode()->GetIndex();
const SwPageFrame* pPage = static_cast<const SwPageFrame*>(pRootFrame->Lower());
if( pPage )
pPage = static_cast<const SwPageFrame*>(pPage->GetNext());
while( pPage )
{
if( nIndex >= m_pImpl->size() )
return false;
const SwLayoutFrame* pLay = pPage->FindBodyCont();
const SwFrame* pTmp = pLay ? pLay->ContainsAny() : nullptr;
if( pTmp && pTmp->IsSctFrame() )
pTmp = static_cast<const SwSectionFrame*>(pTmp)->ContainsAny();
if( pTmp )
{
if( pTmp->IsTextFrame() )
{
SwTextFrame const*const pFrame(static_cast<SwTextFrame const*>(pTmp));
assert(!pFrame->GetMergedPara());
SwNodeOffset nNdIdx = pFrame->GetTextNodeFirst()->GetIndex();
if( nNdIdx > nStartOfContent )
{
bool bFollow = static_cast<const SwTextFrame*>(pTmp)->IsFollow();
nNdIdx -= nStartOfContent;
if( m_pImpl->GetBreakIndex( nIndex ) != nNdIdx ||
SW_LAYCACHE_IO_REC_PARA !=
m_pImpl->GetBreakType( nIndex ) ||
(bFollow
? sal_Int32(static_cast<const SwTextFrame*>(pTmp)->GetOffset())
: COMPLETE_STRING) != m_pImpl->GetBreakOfst(nIndex))
{
return false;
}
++nIndex;
}
}
else if( pTmp->IsTabFrame() )
{
const SwTabFrame* pTab = static_cast<const SwTabFrame*>(pTmp);
sal_Int32 nOfst = COMPLETE_STRING;
if( pTab->IsFollow() )
{
nOfst = 0;
if( pTab->IsFollow() )
pTab = pTab->FindMaster( true );
while( pTab != pTmp )
{
const SwFrame* pSub = pTab->Lower();
while( pSub )
{
++nOfst;
pSub = pSub->GetNext();
}
pTab = pTab->GetFollow();
}
}
do
{
SwNodeOffset nNdIdx =
pTab->GetTable()->GetTableNode()->GetIndex();
if( nNdIdx > nStartOfContent )
{
nNdIdx -= nStartOfContent;
if( m_pImpl->GetBreakIndex( nIndex ) != nNdIdx ||
SW_LAYCACHE_IO_REC_TABLE !=
m_pImpl->GetBreakType( nIndex ) ||
nOfst != m_pImpl->GetBreakOfst( nIndex ) )
{
return false;
}
++nIndex;
}
if( pTab->GetFollow() )
{
if( nOfst == COMPLETE_STRING )
nOfst = 0;
do
{
const SwFrame* pSub = pTab->Lower();
while( pSub )
{
++nOfst;
pSub = pSub->GetNext();
}
pTab = pTab->GetFollow();
assert(pTab && "Table follow without master");
const SwPageFrame *pTabPage = pTab->FindPageFrame();
if( pTabPage != pPage )
{
pPage = pTabPage;
break;
}
} while ( pTab->GetFollow() );
}
else
break;
} while( pTab );
}
}
pPage = static_cast<const SwPageFrame*>(pPage->GetNext());
}
}
return true;
}
#endif
void SwLayoutCache::ClearImpl()
{
if( !IsLocked() )
{
m_pImpl.reset();
}
}
SwLayoutCache::~SwLayoutCache()
{
OSL_ENSURE( !m_nLockCount, "Deleting a locked SwLayoutCache!?" );
}
/// helper class to create not nested section frames for nested sections.
SwActualSection::SwActualSection( SwActualSection *pUp,
SwSectionFrame *pSect,
SwSectionNode *pNd ) :
m_pUpper( pUp ),
m_pSectFrame( pSect ),
m_pSectNode( pNd )
{
if ( !m_pSectNode )
{
const SwNodeIndex *pIndex = pSect->GetFormat()->GetContent().GetContentIdx();
m_pSectNode = pIndex->GetNode().FindSectionNode();
}
}
namespace {
bool sanityCheckLayoutCache(SwLayCacheImpl const& rCache,
SwNodes const& rNodes, SwNodeOffset nNodeIndex)
{
auto const nStartOfContent(rNodes.GetEndOfContent().StartOfSectionNode()->GetIndex());
nNodeIndex -= nStartOfContent;
auto const nMaxIndex(rNodes.GetEndOfContent().GetIndex() - nStartOfContent);
for (size_t nIndex = 0; nIndex < rCache.size(); ++nIndex)
{
auto const nBreakIndex(rCache.GetBreakIndex(nIndex));
if (nBreakIndex < nNodeIndex || nMaxIndex <= nBreakIndex)
{
SAL_WARN("sw.layout",
"invalid node index in layout-cache: " << nBreakIndex);
return false;
}
auto const nBreakType(rCache.GetBreakType(nIndex));
switch (nBreakType)
{
case SW_LAYCACHE_IO_REC_PARA:
if (!rNodes[nBreakIndex + nStartOfContent]->IsTextNode())
{
SAL_WARN("sw.layout",
"invalid node of type 'P' in layout-cache");
return false;
}
break;
case SW_LAYCACHE_IO_REC_TABLE:
if (!rNodes[nBreakIndex + nStartOfContent]->IsTableNode())
{
SAL_WARN("sw.layout",
"invalid node of type 'T' in layout-cache");
return false;
}
break;
default:
assert(false); // Read shouldn't have inserted that
}
}
return true;
}
} // namespace
/** helper class, which utilizes the layout cache information
* to distribute the document content to the right pages.
* It's used by the InsertCnt_(..)-function.
* If there's no layout cache, the distribution to the pages is more
* a guess, but a guess with statistical background.
*/
SwLayHelper::SwLayHelper( SwDoc *pD, SwFrame* &rpF, SwFrame* &rpP, SwPageFrame* &rpPg,
SwLayoutFrame* &rpL, std::unique_ptr<SwActualSection> &rpA,
SwNodeOffset nNodeIndex, bool bCache )
: mrpFrame( rpF )
, mrpPrv( rpP )
, mrpPage( rpPg )
, mrpLay( rpL )
, mrpActualSection( rpA )
, mbBreakAfter(false)
, mpDoc(pD)
, mnMaxParaPerPage( 25 )
, mnParagraphCnt( bCache ? 0 : USHRT_MAX )
, mnFlyIdx( 0 )
, mbFirst( bCache )
{
mpImpl = mpDoc->GetLayoutCache() ? mpDoc->GetLayoutCache()->LockImpl() : nullptr;
if( mpImpl )
{
SwNodes const& rNodes(mpDoc->GetNodes());
if (sanityCheckLayoutCache(*mpImpl, rNodes, nNodeIndex))
{
mnIndex = 0;
mnStartOfContent = rNodes.GetEndOfContent().StartOfSectionNode()->GetIndex();
mnMaxParaPerPage = 1000;
}
else
{
mpDoc->GetLayoutCache()->UnlockImpl();
mpImpl = nullptr;
mnIndex = std::numeric_limits<size_t>::max();
mnStartOfContent = SwNodeOffset(USHRT_MAX);
}
}
else
{
mnIndex = std::numeric_limits<size_t>::max();
mnStartOfContent = NODE_OFFSET_MAX;
}
}
SwLayHelper::~SwLayHelper()
{
if( mpImpl )
{
assert(mpDoc && mpDoc->GetLayoutCache() && "Missing layoutcache");
mpDoc->GetLayoutCache()->UnlockImpl();
}
}
/** Does NOT really calculate the page count,
* it returns the page count value from the layout cache, if available,
* otherwise it estimates the page count.
*/
sal_uLong SwLayHelper::CalcPageCount()
{
sal_uLong nPgCount;
SwLayCacheImpl *pCache = mpDoc->GetLayoutCache() ?
mpDoc->GetLayoutCache()->LockImpl() : nullptr;
if( pCache )
{
nPgCount = pCache->size() + 1;
mpDoc->GetLayoutCache()->UnlockImpl();
}
else
{
nPgCount = mpDoc->getIDocumentStatistics().GetDocStat().nPage;
if ( nPgCount <= 10 ) // no page insertion for less than 10 pages
nPgCount = 0;
sal_Int32 nNdCount = mpDoc->getIDocumentStatistics().GetDocStat().nPara;
if ( nNdCount <= 1 )
{
//Estimates the number of paragraphs.
SwNodeOffset nTmp = mpDoc->GetNodes().GetEndOfContent().GetIndex() -
mpDoc->GetNodes().GetEndOfExtras().GetIndex();
//Tables have a little overhead...
nTmp -= SwNodeOffset(mpDoc->GetTableFrameFormats()->size() * 25);
//Fly frames, too ..
nTmp -= (mpDoc->GetNodes().GetEndOfAutotext().GetIndex() -
mpDoc->GetNodes().GetEndOfInserts().GetIndex()) / SwNodeOffset(3 * 5);
if ( nTmp > SwNodeOffset(0) )
nNdCount = sal_Int32(nTmp);
}
if ( nNdCount > 100 ) // no estimation below this value
{
if ( nPgCount > 0 )
{ // tdf#129529 avoid 0...
mnMaxParaPerPage = std::max<sal_uLong>(3, nNdCount / nPgCount);
}
else
{
mnMaxParaPerPage = std::max( sal_uLong(20),
sal_uLong(20 + nNdCount / 1000 * 3) );
const sal_uLong nMax = 53;
mnMaxParaPerPage = std::min( mnMaxParaPerPage, nMax );
nPgCount = nNdCount / mnMaxParaPerPage;
}
if ( nNdCount < 1000 )
nPgCount = 0;// no progress bar for small documents
SwViewShell *pSh = nullptr;
if( mrpLay && mrpLay->getRootFrame() )
pSh = mrpLay->getRootFrame()->GetCurrShell();
if( pSh && pSh->GetViewOptions()->getBrowseMode() )
mnMaxParaPerPage *= 6;
}
}
return nPgCount;
}
/**
* inserts a page and return true, if
* - the break after flag is set
* - the actual content wants a break before
* - the maximum count of paragraph/rows is reached
*
* The break after flag is set, if the actual content
* wants a break after.
*/
bool SwLayHelper::CheckInsertPage(
SwPageFrame *& rpPage,
SwLayoutFrame *& rpLay,
SwFrame *& rpFrame,
bool & rIsBreakAfter,
bool const isForceBreak)
{
bool bEnd = nullptr == rpPage->GetNext();
const SvxFormatBreakItem& rBrk = rpFrame->GetBreakItem();
const SwFormatPageDesc& rDesc = rpFrame->GetPageDescItem();
// #118195# Do not evaluate page description if frame
// is a follow frame!
const SwPageDesc* pDesc = rpFrame->IsFlowFrame()
&& SwFlowFrame::CastFlowFrame(rpFrame)->IsFollow()
? nullptr
: rDesc.GetPageDesc();
bool bBrk = isForceBreak || rIsBreakAfter;
rIsBreakAfter = rBrk.GetBreak() == SvxBreak::PageAfter ||
rBrk.GetBreak() == SvxBreak::PageBoth;
if ( !bBrk )
bBrk = rBrk.GetBreak() == SvxBreak::PageBefore ||
rBrk.GetBreak() == SvxBreak::PageBoth;
if ( bBrk || pDesc )
{
::std::optional<sal_uInt16> oPgNum;
if ( !pDesc )
{
pDesc = rpPage->GetPageDesc()->GetFollow();
}
else
{
oPgNum = rDesc.GetNumOffset();
if ( oPgNum )
static_cast<SwRootFrame*>(rpPage->GetUpper())->SetVirtPageNum(true);
}
bool bNextPageRight = !rpPage->OnRightPage();
bool bInsertEmpty = false;
assert(rpPage->GetUpper()->GetLower());
if (oPgNum && bNextPageRight != IsRightPageByNumber(
*static_cast<SwRootFrame*>(rpPage->GetUpper()), *oPgNum))
{
bNextPageRight = !bNextPageRight;
bInsertEmpty = true;
}
// If the page style is changing, we'll have a first page.
bool bNextPageFirst = pDesc != rpPage->GetPageDesc();
::InsertNewPage( const_cast<SwPageDesc&>(*pDesc), rpPage->GetUpper(),
bNextPageRight, bNextPageFirst, bInsertEmpty, false, rpPage->GetNext());
if ( bEnd )
{
OSL_ENSURE( rpPage->GetNext(), "No new page?" );
do
{
rpPage = static_cast<SwPageFrame*>(rpPage->GetNext());
} while (rpPage->GetNext());
}
else
{
OSL_ENSURE( rpPage->GetNext(), "No new page?" );
rpPage = static_cast<SwPageFrame*>(rpPage->GetNext());
if (rpPage->IsEmptyPage())
{
OSL_ENSURE( rpPage->GetNext(), "No new page?" );
rpPage = static_cast<SwPageFrame*>(rpPage->GetNext());
}
}
rpLay = rpPage->FindBodyCont();
while (rpLay->Lower())
rpLay = static_cast<SwLayoutFrame*>(rpLay->Lower());
return true;
}
return false;
}
/** entry point for the InsertCnt_-function.
* The document content index is checked either it is
* in the layout cache either it's time to insert a page
* cause the maximal estimation of content per page is reached.
* A really big table or long paragraph may contains more than
* one page, in this case the needed count of pages will inserted.
*/
bool SwLayHelper::CheckInsert( SwNodeOffset nNodeIndex )
{
bool bRet = false;
bool bLongTab = false;
sal_uLong nMaxRowPerPage( 0 );
nNodeIndex -= mnStartOfContent;
sal_uInt16 nRows( 0 );
if( mrpFrame->IsTabFrame() )
{
//Inside a table counts every row as a paragraph
SwFrame *pLow = static_cast<SwTabFrame*>(mrpFrame)->Lower();
nRows = 0;
do
{
++nRows;
pLow = pLow->GetNext();
} while ( pLow );
mnParagraphCnt += nRows;
if( !mpImpl && mnParagraphCnt > mnMaxParaPerPage + 10 )
{
// OD 09.04.2003 #108698# - improve heuristics:
// Assume that a table, which has more than three times the quantity
// of maximal paragraphs per page rows, consists of rows, which have
// the height of a normal paragraph. Thus, allow as much rows per page
// as much paragraphs are allowed.
if ( nRows > ( 3*mnMaxParaPerPage ) )
{
nMaxRowPerPage = mnMaxParaPerPage;
}
else
{
SwFrame *pTmp = static_cast<SwTabFrame*>(mrpFrame)->Lower();
if( pTmp->GetNext() )
pTmp = pTmp->GetNext();
pTmp = static_cast<SwRowFrame*>(pTmp)->Lower();
sal_uInt16 nCnt = 0;
do
{
++nCnt;
pTmp = pTmp->GetNext();
} while( pTmp );
nMaxRowPerPage = std::max( sal_uLong(2), mnMaxParaPerPage / nCnt );
}
bLongTab = true;
}
}
else
++mnParagraphCnt;
if( mbFirst && mpImpl && mnIndex < mpImpl->size() &&
mpImpl->GetBreakIndex( mnIndex ) == nNodeIndex &&
( mpImpl->GetBreakOfst( mnIndex ) < COMPLETE_STRING ||
( ++mnIndex < mpImpl->size() &&
mpImpl->GetBreakIndex( mnIndex ) == nNodeIndex ) ) )
mbFirst = false;
// OD 09.04.2003 #108698# - always split a big tables.
if ( !mbFirst ||
( mrpFrame->IsTabFrame() && bLongTab )
)
{
sal_Int32 nRowCount = 0;
do
{
if( mpImpl || bLongTab )
{
sal_Int32 nOfst = COMPLETE_STRING;
sal_uInt16 nType = SW_LAYCACHE_IO_REC_PAGES;
if( bLongTab )
{
mbBreakAfter = true;
nOfst = static_cast<sal_Int32>(nRowCount + nMaxRowPerPage);
}
else
{
while( mnIndex < mpImpl->size() &&
mpImpl->GetBreakIndex(mnIndex) < nNodeIndex)
++mnIndex;
if( mnIndex < mpImpl->size() &&
mpImpl->GetBreakIndex(mnIndex) == nNodeIndex )
{
nType = mpImpl->GetBreakType( mnIndex );
nOfst = mpImpl->GetBreakOfst( mnIndex++ );
mbBreakAfter = true;
}
}
if( nOfst < COMPLETE_STRING )
{
bool bSplit = false;
sal_uInt16 nRepeat( 0 );
if( !bLongTab && mrpFrame->IsTextFrame() &&
SW_LAYCACHE_IO_REC_PARA == nType &&
nOfst < static_cast<SwTextFrame*>(mrpFrame)->GetText().getLength())
bSplit = true;
else if( mrpFrame->IsTabFrame() && nRowCount < nOfst &&
( bLongTab || SW_LAYCACHE_IO_REC_TABLE == nType ) )
{
nRepeat = static_cast<SwTabFrame*>(mrpFrame)->
GetTable()->GetRowsToRepeat();
bSplit = nOfst < nRows && nRowCount + nRepeat < nOfst;
bLongTab = bLongTab && bSplit;
}
if( bSplit )
{
mrpFrame->InsertBehind( mrpLay, mrpPrv );
{
SwFrameAreaDefinition::FrameAreaWriteAccess aFrm(*mrpFrame);
aFrm.Pos() = mrpLay->getFrameArea().Pos();
aFrm.Pos().AdjustY(1 );
}
mrpPrv = mrpFrame;
if( mrpFrame->IsTabFrame() )
{
SwTabFrame* pTab = static_cast<SwTabFrame*>(mrpFrame);
// #i33629#, #i29955#
::RegistFlys( pTab->FindPageFrame(), pTab );
SwFrame *pRow = pTab->Lower();
SwTabFrame *pFoll = new SwTabFrame( *pTab );
SwFrame *pPrv;
if( nRepeat > 0 )
{
sw::FlyCreationSuppressor aSuppressor;
// Insert new headlines:
sal_uInt16 nRowIdx = 0;
SwRowFrame* pHeadline = nullptr;
while( nRowIdx < nRepeat )
{
OSL_ENSURE( pTab->GetTable()->GetTabLines()[ nRowIdx ], "Table without rows?" );
pHeadline =
new SwRowFrame( *pTab->GetTable()->GetTabLines()[ nRowIdx ], pTab );
pHeadline->SetRepeatedHeadline( true );
pHeadline->InsertBefore( pFoll, nullptr );
pHeadline->RegistFlys();
++nRowIdx;
}
pPrv = pHeadline;
nRows = nRows + nRepeat;
}
else
pPrv = nullptr;
while( pRow && nRowCount < nOfst )
{
pRow = pRow->GetNext();
++nRowCount;
}
while ( pRow )
{
SwFrame* pNxt = pRow->GetNext();
pRow->RemoveFromLayout();
pRow->InsertBehind( pFoll, pPrv );
pPrv = pRow;
pRow = pNxt;
}
mrpFrame = pFoll;
}
else
{
SwTextFrame *const pNew = static_cast<SwTextFrame*>(
static_cast<SwTextFrame*>(mrpFrame)
->GetTextNodeFirst()->MakeFrame(mrpFrame));
pNew->ManipOfst( TextFrameIndex(nOfst) );
pNew->SetFollow( static_cast<SwTextFrame*>(mrpFrame)->GetFollow() );
static_cast<SwTextFrame*>(mrpFrame)->SetFollow( pNew );
mrpFrame = pNew;
}
}
}
}
SwPageFrame* pLastPage = mrpPage;
if (CheckInsertPage(mrpPage, mrpLay, mrpFrame, mbBreakAfter, mnMaxParaPerPage < mnParagraphCnt))
{
CheckFlyCache_( pLastPage );
if( mrpPrv && mrpPrv->IsTextFrame() && !mrpPrv->isFrameAreaSizeValid() )
{
SwFrameAreaDefinition::FrameAreaWriteAccess aFrm(*mrpPrv);
aFrm.Height( mrpPrv->GetUpper()->getFramePrintArea().Height() );
}
bRet = true;
mrpPrv = nullptr;
mnParagraphCnt = 0;
if ( mrpActualSection )
{
//Did the SectionFrame even have a content? If not, we can
//directly put it somewhere else
SwSectionFrame *pSct;
bool bInit = false;
if ( !mrpActualSection->GetSectionFrame()->ContainsContent())
{
pSct = mrpActualSection->GetSectionFrame();
pSct->RemoveFromLayout();
}
else
{
pSct = new SwSectionFrame(
*mrpActualSection->GetSectionFrame(), false );
mrpActualSection->GetSectionFrame()->SimpleFormat();
bInit = true;
}
mrpActualSection->SetSectionFrame( pSct );
pSct->InsertBehind( mrpLay, nullptr );
if( bInit )
{
pSct->Init();
}
{
SwFrameAreaDefinition::FrameAreaWriteAccess aFrm(*pSct);
aFrm.Pos() = mrpLay->getFrameArea().Pos();
aFrm.Pos().AdjustY(1 ); //because of the notifications
}
mrpLay = pSct;
if ( mrpLay->Lower() && mrpLay->Lower()->IsLayoutFrame() )
mrpLay = mrpLay->GetNextLayoutLeaf();
}
}
} while( bLongTab || ( mpImpl && mnIndex < mpImpl->size() &&
mpImpl->GetBreakIndex( mnIndex ) == nNodeIndex ) );
}
mbFirst = false;
return bRet;
}
namespace {
struct SdrObjectCompare
{
bool operator()( const SdrObject* pF1, const SdrObject* pF2 ) const
{
return pF1->GetOrdNum() < pF2->GetOrdNum();
}
};
struct FlyCacheCompare
{
bool operator()( const SwFlyCache* pC1, const SwFlyCache* pC2 ) const
{
return pC1->nOrdNum < pC2->nOrdNum;
}
};
}
/**
* If a new page is inserted, the last page is analysed.
* If there are text frames with default position, the fly cache
* is checked, if these frames are stored in the cache.
*/
void SwLayHelper::CheckFlyCache_( SwPageFrame* pPage )
{
if( !mpImpl || !pPage )
return;
const size_t nFlyCount = mpImpl->GetFlyCount();
// Any text frames at the page, fly cache available?
if( !(pPage->GetSortedObjs() && mnFlyIdx < nFlyCount) )
return;
SwSortedObjs &rObjs = *pPage->GetSortedObjs();
sal_uInt16 nPgNum = pPage->GetPhyPageNum();
// NOTE: Here we do not use the absolute ordnums but
// relative ordnums for the objects on this page.
// skip fly frames from pages before the current page
while( mnFlyIdx < nFlyCount &&
mpImpl->GetFlyCache(mnFlyIdx).nPageNum < nPgNum )
++mnFlyIdx;
// sort cached objects on this page by ordnum
o3tl::sorted_vector< const SwFlyCache*, FlyCacheCompare > aFlyCacheSet;
size_t nIdx = mnFlyIdx;
SwFlyCache* pFlyC;
while( nIdx < nFlyCount &&
( pFlyC = &mpImpl->GetFlyCache( nIdx ) )->nPageNum == nPgNum )
{
aFlyCacheSet.insert( pFlyC );
++nIdx;
}
// sort objects on this page by ordnum
o3tl::sorted_vector< const SdrObject*, SdrObjectCompare > aFlySet;
for (SwAnchoredObject* pAnchoredObj : rObjs)
{
if (SwFlyFrame *pFly = pAnchoredObj->DynCastFlyFrame()) // a text frame?
{
if( pFly->GetAnchorFrame() &&
!pFly->GetAnchorFrame()->FindFooterOrHeader() )
{
const SwContact *pC = ::GetUserCall( pAnchoredObj->GetDrawObj() );
if( pC )
{
aFlySet.insert( pAnchoredObj->GetDrawObj() );
}
}
}
}
if ( aFlyCacheSet.size() != aFlySet.size() )
return;
auto aFlySetIt = aFlySet.begin();
for ( const SwFlyCache* pFlyCache : aFlyCacheSet )
{
SwFlyFrame* pFly = const_cast<SwVirtFlyDrawObj*>(static_cast<const SwVirtFlyDrawObj*>(*aFlySetIt))->GetFlyFrame();
if ( pFly->getFrameArea().Left() == FAR_AWAY )
{
// we get the stored information
SwFrameAreaDefinition::FrameAreaWriteAccess aFrm(*pFly);
aFrm.Pos().setX( pFlyCache->Left() + pPage->getFrameArea().Left() );
aFrm.Pos().setY( pFlyCache->Top() + pPage->getFrameArea().Top() );
if ( mpImpl->IsUseFlyCache() )
{
aFrm.Width( pFlyCache->Width() );
aFrm.Height( pFlyCache->Height() );
}
}
++aFlySetIt;
}
}
SwLayCacheIoImpl::SwLayCacheIoImpl( SvStream& rStrm, bool bWrtMd ) :
m_pStream( &rStrm ),
m_nFlagRecEnd ( 0 ),
m_nMajorVersion(SW_LAYCACHE_IO_VERSION_MAJOR),
m_nMinorVersion(SW_LAYCACHE_IO_VERSION_MINOR),
m_bWriteMode( bWrtMd ),
m_bError( false )
{
if( m_bWriteMode )
m_pStream->WriteUInt16( m_nMajorVersion )
.WriteUInt16( m_nMinorVersion );
else
m_pStream->ReadUInt16( m_nMajorVersion )
.ReadUInt16( m_nMinorVersion );
}
void SwLayCacheIoImpl::OpenRec( sal_uInt8 cType )
{
sal_uInt64 nPos = m_pStream->Tell();
if( m_bWriteMode )
{
m_aRecords.emplace_back(cType, nPos );
m_pStream->WriteUInt32( 0 );
}
else
{
sal_uInt32 nVal(0);
m_pStream->ReadUInt32( nVal );
sal_uInt8 cRecTyp = static_cast<sal_uInt8>(nVal);
if (!nVal || cRecTyp != cType || !m_pStream->good())
{
OSL_ENSURE( nVal, "OpenRec: Record-Header is 0" );
OSL_ENSURE( cRecTyp == cType, "OpenRec: Wrong Record Type" );
m_aRecords.emplace_back(0, m_pStream->Tell() );
m_bError = true;
}
else
{
sal_uInt32 nSize = nVal >> 8;
m_aRecords.emplace_back(cRecTyp, nPos+nSize );
}
}
}
// Close record
void SwLayCacheIoImpl::CloseRec()
{
bool bRes = true;
OSL_ENSURE( !m_aRecords.empty(), "CloseRec: no levels" );
if( !m_aRecords.empty() )
{
sal_uInt64 nPos = m_pStream->Tell();
if( m_bWriteMode )
{
sal_uInt32 nBgn = m_aRecords.back().size;
m_pStream->Seek( nBgn );
sal_uInt32 nSize = nPos - nBgn;
sal_uInt32 nVal = ( nSize << 8 ) | m_aRecords.back().type;
m_pStream->WriteUInt32( nVal );
m_pStream->Seek( nPos );
if( m_pStream->GetError() != ERRCODE_NONE )
bRes = false;
}
else
{
sal_uInt32 n = m_aRecords.back().size;
OSL_ENSURE( n >= nPos, "CloseRec: too much data read" );
if( n != nPos )
{
m_pStream->Seek( n );
if( n < nPos )
bRes = false;
}
if( m_pStream->GetErrorCode() != ERRCODE_NONE )
bRes = false;
}
m_aRecords.pop_back();
}
if( !bRes )
m_bError = true;
}
sal_uInt32 SwLayCacheIoImpl::BytesLeft()
{
sal_uInt32 n = 0;
if( !m_bError && !m_aRecords.empty() )
{
sal_uInt32 nEndPos = m_aRecords.back().size;
sal_uInt64 nPos = m_pStream->Tell();
if( nEndPos > nPos )
n = nEndPos - nPos;
}
return n;
}
sal_uInt8 SwLayCacheIoImpl::Peek()
{
sal_uInt8 c(0);
if( !m_bError )
{
sal_uInt64 nPos = m_pStream->Tell();
m_pStream->ReadUChar( c );
m_pStream->Seek( nPos );
if( m_pStream->GetErrorCode() != ERRCODE_NONE )
{
c = 0;
m_bError = true;
}
}
return c;
}
void SwLayCacheIoImpl::SkipRec()
{
sal_uInt8 c = Peek();
OpenRec( c );
m_pStream->Seek( m_aRecords.back().size );
CloseRec();
}
sal_uInt8 SwLayCacheIoImpl::OpenFlagRec()
{
OSL_ENSURE( !m_bWriteMode, "OpenFlagRec illegal in write mode" );
sal_uInt8 cFlags(0);
m_pStream->ReadUChar( cFlags );
m_nFlagRecEnd = m_pStream->Tell() + ( cFlags & 0x0F );
return (cFlags >> 4);
}
void SwLayCacheIoImpl::OpenFlagRec( sal_uInt8 nFlags, sal_uInt8 nLen )
{
OSL_ENSURE( m_bWriteMode, "OpenFlagRec illegal in read mode" );
OSL_ENSURE( (nFlags & 0xF0) == 0, "illegal flags set" );
OSL_ENSURE( nLen < 16, "wrong flag record length" );
sal_uInt8 cFlags = (nFlags << 4) + nLen;
m_pStream->WriteUChar( cFlags );
m_nFlagRecEnd = m_pStream->Tell() + nLen;
}
void SwLayCacheIoImpl::CloseFlagRec()
{
if( m_bWriteMode )
{
OSL_ENSURE( m_pStream->Tell() == m_nFlagRecEnd, "Wrong amount of data written" );
}
else
{
OSL_ENSURE( m_pStream->Tell() <= m_nFlagRecEnd, "Too many data read" );
if( m_pStream->Tell() != m_nFlagRecEnd )
m_pStream->Seek( m_nFlagRecEnd );
}
}
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
↑ V1028 Possible overflow. Consider casting operands of the '20 + nNdCount / 1000 * 3' operator to the 'sal_uLong' type, not the result.