office-gobmx/sw/source/core/text/inftxt.cxx
Justin Luth 5c6c6a73e9 tdf#43767 mso-format layout: no smallcaps applied to numbering
If the paragraph marker is formatted as Uppercase,
then Uppercase is applied to that line's numbering as well.
However, if the marker is formatted as SmallCaps,
it MUST NOT be applied for MSO formats.

Apparently MSO only supports Uppercase and SmallCaps,
not Lowercase or Titlease.

I don't like these adhoc exceptions, so I didn't
attempt to apply them to ODF formats.
Let's keep it simple for ODF - any char format that applies
to the entire paragraph should apply to numbering as well
(except for the existing underline/overline exceptions).
- if you don't like that char attributes apply at all, blame MSO.
- if you don't like that DOCX is missing your goofy formatting,
  blame MSO for being inconsistent.

ooxmlexport12's tdf143384_tableInFoot_negativeMargins.docx
is almost interesting because it applies superscript
and small caps. However, the list is already uppercase,
so it can't be used for the test.

make CppunitTest_sw_ooxmlexport21 \
    CPPUNIT_TEST_NAME=testTdf43767_caseMapNumbering

Change-Id: I273baebc996adfd001e1c591dbb9aef9272a42f0
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/169476
Reviewed-by: Justin Luth <jluth@mail.com>
Tested-by: Jenkins
2024-06-28 01:24:36 +02:00

2219 lines
75 KiB
C++

/* -*- 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 <com/sun/star/linguistic2/XHyphenator.hpp>
#include <unotools/linguprops.hxx>
#include <unotools/lingucfg.hxx>
#include <fmtinfmt.hxx>
#include <hintids.hxx>
#include <txatbase.hxx>
#include <svl/ctloptions.hxx>
#include <sfx2/infobar.hxx>
#include <sfx2/printer.hxx>
#include <sal/log.hxx>
#include <editeng/hyphenzoneitem.hxx>
#include <editeng/hngpnctitem.hxx>
#include <editeng/scriptspaceitem.hxx>
#include <editeng/splwrap.hxx>
#include <editeng/pgrditem.hxx>
#include <editeng/tstpitem.hxx>
#include <editeng/shaditem.hxx>
#include <SwSmartTagMgr.hxx>
#include <breakit.hxx>
#include <editeng/forbiddenruleitem.hxx>
#include <swmodule.hxx>
#include <vcl/svapp.hxx>
#include <viewsh.hxx>
#include <viewopt.hxx>
#include <frmtool.hxx>
#include <fmturl.hxx>
#include <IDocumentSettingAccess.hxx>
#include <IDocumentDeviceAccess.hxx>
#include <IDocumentMarkAccess.hxx>
#include <paratr.hxx>
#include <rootfrm.hxx>
#include "inftxt.hxx"
#include <noteurl.hxx>
#include "porfly.hxx"
#include "porftn.hxx"
#include "porrst.hxx"
#include "itratr.hxx"
#include "portab.hxx"
#include <wrong.hxx>
#include <doc.hxx>
#include <pam.hxx>
#include <numrule.hxx>
#include <EnhancedPDFExportHelper.hxx>
#include <docsh.hxx>
#include <strings.hrc>
#include <o3tl/deleter.hxx>
#include <vcl/gdimtf.hxx>
#include <vcl/virdev.hxx>
#include <vcl/gradient.hxx>
#include <i18nlangtag/mslangid.hxx>
#include <formatlinebreak.hxx>
#include <view.hxx>
#include <wrtsh.hxx>
#include <com/sun/star/text/XTextRange.hpp>
#include <unotextrange.hxx>
#include <SwStyleNameMapper.hxx>
#include <unoprnms.hxx>
#include <editeng/unoprnms.hxx>
#include <unomap.hxx>
#include <com/sun/star/awt/FontSlant.hpp>
using namespace ::com::sun::star;
using namespace ::com::sun::star::linguistic2;
using namespace ::com::sun::star::uno;
using namespace ::com::sun::star::beans;
#define CHAR_LEFT_ARROW u'\x25C0'
#define CHAR_RIGHT_ARROW u'\x25B6'
#define CHAR_TAB u'\x2192'
#define CHAR_TAB_RTL u'\x2190'
#define CHAR_LINEBREAK u'\x21B5'
#define CHAR_LINEBREAK_RTL u'\x21B3'
#define DRAW_SPECIAL_OPTIONS_CENTER 1
#define DRAW_SPECIAL_OPTIONS_ROTATE 2
SwLineInfo::SwLineInfo()
: m_pSpace( nullptr ),
m_nVertAlign( SvxParaVertAlignItem::Align::Automatic ),
m_nDefTabStop( 0 ),
m_bListTabStopIncluded( false ),
m_nListTabStopPosition( 0 )
{
}
SwLineInfo::~SwLineInfo()
{
}
void SwLineInfo::CtorInitLineInfo( const SwAttrSet& rAttrSet,
const SwTextNode& rTextNode )
{
m_oRuler.emplace( rAttrSet.GetTabStops() );
if ( rTextNode.GetListTabStopPosition( m_nListTabStopPosition ) )
{
m_bListTabStopIncluded = true;
// insert the list tab stop into SvxTabItem instance <pRuler>
const SvxTabStop aListTabStop( m_nListTabStopPosition,
SvxTabAdjust::Left );
m_oRuler->Insert( aListTabStop );
// remove default tab stops, which are before the inserted list tab stop
for ( sal_uInt16 i = 0; i < m_oRuler->Count(); i++ )
{
if ( (*m_oRuler)[i].GetTabPos() < m_nListTabStopPosition &&
(*m_oRuler)[i].GetAdjustment() == SvxTabAdjust::Default )
{
m_oRuler->Remove(i);
continue;
}
}
}
if ( !rTextNode.getIDocumentSettingAccess()->get(DocumentSettingId::TABS_RELATIVE_TO_INDENT) )
{
// remove default tab stop at position 0
for ( sal_uInt16 i = 0; i < m_oRuler->Count(); i++ )
{
if ( (*m_oRuler)[i].GetTabPos() == 0 &&
(*m_oRuler)[i].GetAdjustment() == SvxTabAdjust::Default )
{
m_oRuler->Remove(i);
break;
}
}
}
m_pSpace = &rAttrSet.GetLineSpacing();
m_nVertAlign = rAttrSet.GetParaVertAlign().GetValue();
m_nDefTabStop = std::numeric_limits<SwTwips>::max();
}
void SwTextInfo::CtorInitTextInfo( SwTextFrame *pFrame )
{
m_pPara = pFrame->GetPara();
m_nTextStart = pFrame->GetOffset();
if (!m_pPara)
{
SAL_WARN("sw.core", "+SwTextInfo::CTOR: missing paragraph information");
pFrame->Format(pFrame->getRootFrame()->GetCurrShell()->GetOut());
m_pPara = pFrame->GetPara();
}
}
SwTextInfo::SwTextInfo( const SwTextInfo &rInf )
: m_pPara( const_cast<SwTextInfo&>(rInf).GetParaPortion() )
, m_nTextStart( rInf.GetTextStart() )
{ }
#if OSL_DEBUG_LEVEL > 0
static void ChkOutDev( const SwTextSizeInfo &rInf )
{
if ( !rInf.GetVsh() )
return;
const OutputDevice* pOut = rInf.GetOut();
const OutputDevice* pRef = rInf.GetRefDev();
OSL_ENSURE( pOut && pRef, "ChkOutDev: invalid output devices" );
}
#endif
static TextFrameIndex GetMinLen( const SwTextSizeInfo &rInf )
{
const TextFrameIndex nTextLen(rInf.GetText().getLength());
if (rInf.GetLen() == TextFrameIndex(COMPLETE_STRING))
return nTextLen;
const TextFrameIndex nInfLen = rInf.GetIdx() + rInf.GetLen();
return std::min(nTextLen, nInfLen);
}
SwTextSizeInfo::SwTextSizeInfo()
: m_pKanaComp(nullptr)
, m_pVsh(nullptr)
, m_pOut(nullptr)
, m_pRef(nullptr)
, m_pFnt(nullptr)
, m_pUnderFnt(nullptr)
, m_pFrame(nullptr)
, m_pOpt(nullptr)
, m_pText(nullptr)
, m_nIdx(0)
, m_nLen(0)
, m_nMeasureLen(COMPLETE_STRING)
, m_nKanaIdx(0)
, m_bOnWin (false)
, m_bNotEOL (false)
, m_bURLNotify(false)
, m_bStopUnderflow(false)
, m_bFootnoteInside(false)
, m_bOtherThanFootnoteInside(false)
, m_bMulti(false)
, m_bFirstMulti(false)
, m_bRuby(false)
, m_bHanging(false)
, m_bScriptSpace(false)
, m_bForbiddenChars(false)
, m_bSnapToGrid(false)
, m_nDirection(0)
{}
SwTextSizeInfo::SwTextSizeInfo( const SwTextSizeInfo &rNew )
: SwTextInfo( rNew ),
m_pKanaComp(rNew.GetpKanaComp()),
m_pVsh(const_cast<SwTextSizeInfo&>(rNew).GetVsh()),
m_pOut(const_cast<SwTextSizeInfo&>(rNew).GetOut()),
m_pRef(const_cast<SwTextSizeInfo&>(rNew).GetRefDev()),
m_pFnt(const_cast<SwTextSizeInfo&>(rNew).GetFont()),
m_pUnderFnt(rNew.GetUnderFnt()),
m_pFrame(rNew.m_pFrame),
m_pOpt(&rNew.GetOpt()),
m_pText(&rNew.GetText()),
m_nIdx(rNew.GetIdx()),
m_nLen(rNew.GetLen()),
m_nMeasureLen(rNew.GetMeasureLen()),
m_nKanaIdx( rNew.GetKanaIdx() ),
m_bOnWin( rNew.OnWin() ),
m_bNotEOL( rNew.NotEOL() ),
m_bURLNotify( rNew.URLNotify() ),
m_bStopUnderflow( rNew.StopUnderflow() ),
m_bFootnoteInside( rNew.IsFootnoteInside() ),
m_bOtherThanFootnoteInside( rNew.IsOtherThanFootnoteInside() ),
m_bMulti( rNew.IsMulti() ),
m_bFirstMulti( rNew.IsFirstMulti() ),
m_bRuby( rNew.IsRuby() ),
m_bHanging( rNew.IsHanging() ),
m_bScriptSpace( rNew.HasScriptSpace() ),
m_bForbiddenChars( rNew.HasForbiddenChars() ),
m_bSnapToGrid( rNew.SnapToGrid() ),
m_nDirection( rNew.GetDirection() )
{
#if OSL_DEBUG_LEVEL > 0
ChkOutDev( *this );
#endif
}
void SwTextSizeInfo::CtorInitTextSizeInfo( OutputDevice* pRenderContext, SwTextFrame *pFrame,
TextFrameIndex const nNewIdx)
{
m_pKanaComp = nullptr;
m_nKanaIdx = 0;
m_pFrame = pFrame;
CtorInitTextInfo( m_pFrame );
SwDoc const& rDoc(m_pFrame->GetDoc());
m_pVsh = m_pFrame->getRootFrame()->GetCurrShell();
// Get the output and reference device
if ( m_pVsh )
{
m_pOut = pRenderContext;
m_pRef = &m_pVsh->GetRefDev();
m_bOnWin = m_pVsh->GetWin() || OUTDEV_WINDOW == m_pOut->GetOutDevType() || m_pVsh->isOutputToWindow();
}
else
{
// Access via StarONE. We do not need a Shell or an active one.
if (rDoc.getIDocumentSettingAccess().get(DocumentSettingId::HTML_MODE))
{
// We can only pick the AppWin here? (there's nothing better to pick?)
m_pOut = Application::GetDefaultDevice();
}
else
m_pOut = rDoc.getIDocumentDeviceAccess().getPrinter(false);
m_pRef = m_pOut;
}
#if OSL_DEBUG_LEVEL > 0
ChkOutDev( *this );
#endif
// Set default layout mode ( LTR or RTL ).
if ( m_pFrame->IsRightToLeft() )
{
m_pOut->SetLayoutMode( vcl::text::ComplexTextLayoutFlags::BiDiStrong | vcl::text::ComplexTextLayoutFlags::BiDiRtl );
m_pRef->SetLayoutMode( vcl::text::ComplexTextLayoutFlags::BiDiStrong | vcl::text::ComplexTextLayoutFlags::BiDiRtl );
m_nDirection = DIR_RIGHT2LEFT;
}
else
{
m_pOut->SetLayoutMode( vcl::text::ComplexTextLayoutFlags::BiDiStrong );
m_pRef->SetLayoutMode( vcl::text::ComplexTextLayoutFlags::BiDiStrong );
m_nDirection = DIR_LEFT2RIGHT;
}
// The Options
m_pOpt = m_pVsh ?
m_pVsh->GetViewOptions() :
SW_MOD()->GetViewOption(rDoc.getIDocumentSettingAccess().get(DocumentSettingId::HTML_MODE)); // Options from Module, due to StarONE
// bURLNotify is set if MakeGraphic prepares it
// TODO: Unwind
m_bURLNotify = pNoteURL && !m_bOnWin;
SetSnapToGrid( m_pFrame->GetTextNodeForParaProps()->GetSwAttrSet().GetParaGrid().GetValue() &&
m_pFrame->IsInDocBody() );
m_pFnt = nullptr;
m_pUnderFnt = nullptr;
m_pText = &m_pFrame->GetText();
m_nIdx = nNewIdx;
m_nLen = m_nMeasureLen = TextFrameIndex(COMPLETE_STRING);
m_bNotEOL = false;
m_bStopUnderflow = m_bFootnoteInside = m_bOtherThanFootnoteInside = false;
m_bMulti = m_bFirstMulti = m_bRuby = m_bHanging = m_bScriptSpace =
m_bForbiddenChars = false;
SetLen( GetMinLen( *this ) );
}
SwTextSizeInfo::SwTextSizeInfo( const SwTextSizeInfo &rNew, const OUString* pText,
TextFrameIndex const nIndex)
: SwTextInfo( rNew ),
m_pKanaComp(rNew.GetpKanaComp()),
m_pVsh(const_cast<SwTextSizeInfo&>(rNew).GetVsh()),
m_pOut(const_cast<SwTextSizeInfo&>(rNew).GetOut()),
m_pRef(const_cast<SwTextSizeInfo&>(rNew).GetRefDev()),
m_pFnt(const_cast<SwTextSizeInfo&>(rNew).GetFont()),
m_pUnderFnt(rNew.GetUnderFnt()),
m_pFrame( rNew.m_pFrame ),
m_pOpt(&rNew.GetOpt()),
m_pText(pText),
m_nIdx(nIndex),
m_nLen(COMPLETE_STRING),
m_nMeasureLen(COMPLETE_STRING),
m_nKanaIdx( rNew.GetKanaIdx() ),
m_bOnWin( rNew.OnWin() ),
m_bNotEOL( rNew.NotEOL() ),
m_bURLNotify( rNew.URLNotify() ),
m_bStopUnderflow( rNew.StopUnderflow() ),
m_bFootnoteInside( rNew.IsFootnoteInside() ),
m_bOtherThanFootnoteInside( rNew.IsOtherThanFootnoteInside() ),
m_bMulti( rNew.IsMulti() ),
m_bFirstMulti( rNew.IsFirstMulti() ),
m_bRuby( rNew.IsRuby() ),
m_bHanging( rNew.IsHanging() ),
m_bScriptSpace( rNew.HasScriptSpace() ),
m_bForbiddenChars( rNew.HasForbiddenChars() ),
m_bSnapToGrid( rNew.SnapToGrid() ),
m_nDirection( rNew.GetDirection() )
{
#if OSL_DEBUG_LEVEL > 0
ChkOutDev( *this );
#endif
SetLen( GetMinLen( *this ) );
}
SwTextSizeInfo::SwTextSizeInfo(SwTextFrame *const pTextFrame,
TextFrameIndex const nIndex)
: m_bOnWin(false)
{
CtorInitTextSizeInfo( pTextFrame->getRootFrame()->GetCurrShell()->GetOut(), pTextFrame, nIndex );
}
void SwTextSizeInfo::SelectFont()
{
// The path needs to go via ChgPhysFnt or the FontMetricCache gets confused.
// In this case pLastMet has it's old value.
// Wrong: GetOut()->SetFont( GetFont()->GetFnt() );
GetFont()->Invalidate();
GetFont()->ChgPhysFnt( m_pVsh, *GetOut() );
}
void SwTextSizeInfo::NoteAnimation() const
{
if( OnWin() )
SwRootFrame::FlushVout();
OSL_ENSURE( m_pOut == m_pVsh->GetOut(),
"SwTextSizeInfo::NoteAnimation() changed m_pOut" );
}
SwPosSize SwTextSizeInfo::GetTextSize( OutputDevice* pOutDev,
const SwScriptInfo* pSI,
const OUString& rText,
const TextFrameIndex nIndex,
const TextFrameIndex nLength) const
{
SwDrawTextInfo aDrawInf(m_pVsh, *pOutDev, pSI, rText, nIndex, nLength,
/*layout context*/ std::nullopt);
aDrawInf.SetFrame( m_pFrame );
aDrawInf.SetFont( m_pFnt );
aDrawInf.SetSnapToGrid( SnapToGrid() );
aDrawInf.SetKanaComp( 0 );
return SwPosSize(m_pFnt->GetTextSize_( aDrawInf ));
}
SwPosSize
SwTextSizeInfo::GetTextSize(std::optional<SwLinePortionLayoutContext> nLayoutContext) const
{
const SwScriptInfo& rSI =
const_cast<SwParaPortion*>(GetParaPortion())->GetScriptInfo();
// in some cases, compression is not allowed or suppressed for
// performance reasons
sal_uInt16 nComp =( SwFontScript::CJK == GetFont()->GetActual() &&
rSI.CountCompChg() &&
! IsMulti() ) ?
GetKanaComp() :
0 ;
SwDrawTextInfo aDrawInf(m_pVsh, *m_pOut, &rSI, *m_pText, m_nIdx, m_nLen, nLayoutContext);
aDrawInf.SetMeasureLen( m_nMeasureLen );
aDrawInf.SetFrame( m_pFrame );
aDrawInf.SetFont( m_pFnt );
aDrawInf.SetSnapToGrid( SnapToGrid() );
aDrawInf.SetKanaComp( nComp );
return SwPosSize(m_pFnt->GetTextSize_( aDrawInf ));
}
void SwTextSizeInfo::GetTextSize(const SwScriptInfo* pSI, const TextFrameIndex nIndex,
const TextFrameIndex nLength,
std::optional<SwLinePortionLayoutContext> nLayoutContext,
const sal_uInt16 nComp, SwTwips& nMinSize,
tools::Long& nMaxSizeDiff, SwTwips& nExtraAscent,
SwTwips& nExtraDescent,
vcl::text::TextLayoutCache const* const pCache) const
{
SwDrawTextInfo aDrawInf(m_pVsh, *m_pOut, pSI, *m_pText, nIndex, nLength, nLayoutContext, 0,
false, pCache);
aDrawInf.SetFrame( m_pFrame );
aDrawInf.SetFont( m_pFnt );
aDrawInf.SetSnapToGrid( SnapToGrid() );
aDrawInf.SetKanaComp( nComp );
SwPosSize aSize( m_pFnt->GetTextSize_( aDrawInf ) );
nMaxSizeDiff = aDrawInf.GetKanaDiff();
nExtraAscent = aDrawInf.GetExtraAscent();
nExtraDescent = aDrawInf.GetExtraDescent();
nMinSize = aSize.Width();
}
TextFrameIndex SwTextSizeInfo::GetTextBreak( const tools::Long nLineWidth,
const TextFrameIndex nMaxLen,
const sal_uInt16 nComp,
vcl::text::TextLayoutCache const*const pCache) const
{
const SwScriptInfo& rScriptInfo =
const_cast<SwParaPortion*>(GetParaPortion())->GetScriptInfo();
OSL_ENSURE( m_pRef == m_pOut, "GetTextBreak is supposed to use the RefDev" );
SwDrawTextInfo aDrawInf(m_pVsh, *m_pOut, &rScriptInfo, *m_pText, GetIdx(), nMaxLen,
/*layout context*/ std::nullopt, 0, false, pCache);
aDrawInf.SetFrame( m_pFrame );
aDrawInf.SetFont( m_pFnt );
aDrawInf.SetSnapToGrid( SnapToGrid() );
aDrawInf.SetKanaComp( nComp );
aDrawInf.SetHyphPos( nullptr );
return m_pFnt->GetTextBreak( aDrawInf, nLineWidth );
}
TextFrameIndex SwTextSizeInfo::GetTextBreak( const tools::Long nLineWidth,
const TextFrameIndex nMaxLen,
const sal_uInt16 nComp,
TextFrameIndex& rExtraCharPos,
vcl::text::TextLayoutCache const*const pCache) const
{
const SwScriptInfo& rScriptInfo =
const_cast<SwParaPortion*>(GetParaPortion())->GetScriptInfo();
OSL_ENSURE( m_pRef == m_pOut, "GetTextBreak is supposed to use the RefDev" );
SwDrawTextInfo aDrawInf(m_pVsh, *m_pOut, &rScriptInfo, *m_pText, GetIdx(), nMaxLen,
/*layout context*/ std::nullopt, 0, false, pCache);
aDrawInf.SetFrame( m_pFrame );
aDrawInf.SetFont( m_pFnt );
aDrawInf.SetSnapToGrid( SnapToGrid() );
aDrawInf.SetKanaComp( nComp );
aDrawInf.SetHyphPos( &rExtraCharPos );
return m_pFnt->GetTextBreak( aDrawInf, nLineWidth );
}
bool SwTextSizeInfo::HasHint(TextFrameIndex const nPos) const
{
std::pair<SwTextNode const*, sal_Int32> const pos(m_pFrame->MapViewToModel(nPos));
return pos.first->GetTextAttrForCharAt(pos.second);
}
void SwTextPaintInfo::CtorInitTextPaintInfo( OutputDevice* pRenderContext, SwTextFrame *pFrame, const SwRect &rPaint )
{
CtorInitTextSizeInfo( pRenderContext, pFrame, TextFrameIndex(0) );
m_aTextFly.CtorInitTextFly( pFrame );
m_aPaintRect = rPaint;
m_nSpaceIdx = 0;
m_pSpaceAdd = nullptr;
m_pWrongList = nullptr;
m_pGrammarCheckList = nullptr;
m_pSmartTags = nullptr;
m_pBrushItem = nullptr;
}
SwTextPaintInfo::SwTextPaintInfo( const SwTextPaintInfo &rInf, const OUString* pText )
: SwTextSizeInfo( rInf, pText )
, m_pWrongList( rInf.GetpWrongList() )
, m_pGrammarCheckList( rInf.GetGrammarCheckList() )
, m_pSmartTags( rInf.GetSmartTags() )
, m_pSpaceAdd( rInf.GetpSpaceAdd() ),
m_pBrushItem( rInf.GetBrushItem() ),
m_aTextFly( rInf.GetTextFly() ),
m_aPos( rInf.GetPos() ),
m_aPaintRect( rInf.GetPaintRect() ),
m_nSpaceIdx( rInf.GetSpaceIdx() )
{ }
SwTextPaintInfo::SwTextPaintInfo( const SwTextPaintInfo &rInf )
: SwTextSizeInfo( rInf )
, m_pWrongList( rInf.GetpWrongList() )
, m_pGrammarCheckList( rInf.GetGrammarCheckList() )
, m_pSmartTags( rInf.GetSmartTags() )
, m_pSpaceAdd( rInf.GetpSpaceAdd() ),
m_pBrushItem( rInf.GetBrushItem() ),
m_aTextFly( rInf.GetTextFly() ),
m_aPos( rInf.GetPos() ),
m_aPaintRect( rInf.GetPaintRect() ),
m_nSpaceIdx( rInf.GetSpaceIdx() )
{ }
SwTextPaintInfo::SwTextPaintInfo( SwTextFrame *pFrame, const SwRect &rPaint )
{
CtorInitTextPaintInfo( pFrame->getRootFrame()->GetCurrShell()->GetOut(), pFrame, rPaint );
}
namespace
{
/**
* Context class that captures the draw operations on rDrawInf's output device for transparency
* purposes.
*/
class SwTransparentTextGuard
{
ScopedVclPtrInstance<VirtualDevice> m_aContentVDev;
GDIMetaFile m_aContentMetafile;
MapMode m_aNewMapMode;
SwRect m_aPorRect;
SwTextPaintInfo& m_rPaintInf;
SwDrawTextInfo& m_rDrawInf;
public:
SwTransparentTextGuard(const SwLinePortion& rPor, SwTextPaintInfo& rPaintInf,
SwDrawTextInfo& rDrawInf);
~SwTransparentTextGuard();
};
SwTransparentTextGuard::SwTransparentTextGuard(const SwLinePortion& rPor,
SwTextPaintInfo& rPaintInf, SwDrawTextInfo& rDrawInf)
: m_aNewMapMode(rPaintInf.GetOut()->GetMapMode())
, m_rPaintInf(rPaintInf)
, m_rDrawInf(rDrawInf)
{
rPaintInf.CalcRect(rPor, &m_aPorRect);
rDrawInf.SetOut(*m_aContentVDev);
m_aContentVDev->SetMapMode(rPaintInf.GetOut()->GetMapMode());
m_aContentMetafile.Record(m_aContentVDev.get());
m_aContentVDev->SetLineColor(rPaintInf.GetOut()->GetLineColor());
m_aContentVDev->SetFillColor(rPaintInf.GetOut()->GetFillColor());
m_aContentVDev->SetFont(rPaintInf.GetOut()->GetFont());
m_aContentVDev->SetDrawMode(rPaintInf.GetOut()->GetDrawMode());
m_aContentVDev->SetSettings(rPaintInf.GetOut()->GetSettings());
m_aContentVDev->SetRefPoint(rPaintInf.GetOut()->GetRefPoint());
}
SwTransparentTextGuard::~SwTransparentTextGuard()
{
m_aContentMetafile.Stop();
m_aContentMetafile.WindStart();
m_aNewMapMode.SetOrigin(m_aPorRect.TopLeft());
m_aContentMetafile.SetPrefMapMode(m_aNewMapMode);
m_aContentMetafile.SetPrefSize(m_aPorRect.SSize());
m_rDrawInf.SetOut(*m_rPaintInf.GetOut());
Gradient aVCLGradient;
sal_uInt8 nTransPercentVcl = 255 - m_rPaintInf.GetFont()->GetColor().GetAlpha();
const Color aTransColor(nTransPercentVcl, nTransPercentVcl, nTransPercentVcl);
aVCLGradient.SetStyle(css::awt::GradientStyle_LINEAR);
aVCLGradient.SetStartColor(aTransColor);
aVCLGradient.SetEndColor(aTransColor);
aVCLGradient.SetAngle(0_deg10);
aVCLGradient.SetBorder(0);
aVCLGradient.SetOfsX(0);
aVCLGradient.SetOfsY(0);
aVCLGradient.SetStartIntensity(100);
aVCLGradient.SetEndIntensity(100);
aVCLGradient.SetSteps(2);
m_rPaintInf.GetOut()->DrawTransparent(m_aContentMetafile, m_aPorRect.TopLeft(),
m_aPorRect.SSize(), aVCLGradient);
}
}
void SwTextPaintInfo::DrawText_( const OUString &rText, const SwLinePortion &rPor,
TextFrameIndex const nStart, TextFrameIndex const nLength,
const bool bKern, const bool bWrong,
const bool bSmartTag,
const bool bGrammarCheck )
{
if( !nLength )
return;
// The SwScriptInfo is useless if we are inside a field portion
SwScriptInfo* pSI = nullptr;
if ( ! rPor.InFieldGrp() )
pSI = &GetParaPortion()->GetScriptInfo();
// in some cases, kana compression is not allowed or suppressed for
// performance reasons
sal_uInt16 nComp = 0;
if ( ! IsMulti() )
nComp = GetKanaComp();
bool bCfgIsAutoGrammar = false;
SvtLinguConfig().GetProperty( UPN_IS_GRAMMAR_AUTO ) >>= bCfgIsAutoGrammar;
const bool bBullet = OnWin() && GetOpt().IsBlank() && IsNoSymbol();
const bool bTmpWrong = bWrong && OnWin() && GetOpt().IsOnlineSpell();
const bool bTmpGrammarCheck = bGrammarCheck && OnWin() && bCfgIsAutoGrammar && GetOpt().IsOnlineSpell();
const bool bTmpSmart = bSmartTag && OnWin() && !GetOpt().IsPagePreview() && SwSmartTagMgr::Get().IsSmartTagsEnabled();
OSL_ENSURE( GetParaPortion(), "No paragraph!");
SwDrawTextInfo aDrawInf(m_pFrame->getRootFrame()->GetCurrShell(), *m_pOut, pSI, rText, nStart,
nLength, rPor.GetLayoutContext(), rPor.Width(), bBullet);
aDrawInf.SetUnderFnt( m_pUnderFnt );
const tools::Long nSpaceAdd = ( rPor.IsBlankPortion() || rPor.IsDropPortion() ||
rPor.InNumberGrp() ) ? 0 : GetSpaceAdd(/*bShrink=*/true);
if ( nSpaceAdd )
{
TextFrameIndex nCharCnt(0);
// #i41860# Thai justified alignment needs some
// additional information:
aDrawInf.SetNumberOfBlanks( rPor.InTextGrp() ?
static_cast<const SwTextPortion&>(rPor).GetSpaceCnt( *this, nCharCnt ) :
TextFrameIndex(0) );
}
aDrawInf.SetSpace( nSpaceAdd );
aDrawInf.SetKanaComp( nComp );
// the font is used to identify the current script via nActual
aDrawInf.SetFont( m_pFnt );
// the frame is used to identify the orientation
aDrawInf.SetFrame( GetTextFrame() );
// we have to know if the paragraph should snap to grid
aDrawInf.SetSnapToGrid( SnapToGrid() );
// for underlining we must know when not to add extra space behind
// a character in justified mode
aDrawInf.SetSpaceStop( ! rPor.GetNextPortion() ||
rPor.GetNextPortion()->InFixMargGrp() ||
rPor.GetNextPortion()->IsHolePortion() );
// Draw text next to the left border
Point aFontPos(m_aPos);
if( m_pFnt->GetLeftBorder() && rPor.InTextGrp() && !static_cast<const SwTextPortion&>(rPor).GetJoinBorderWithPrev() )
{
const sal_uInt16 nLeftBorderSpace = m_pFnt->GetLeftBorderSpace();
if ( GetTextFrame()->IsRightToLeft() )
{
aFontPos.AdjustX( -nLeftBorderSpace );
}
else
{
switch( m_pFnt->GetOrientation(GetTextFrame()->IsVertical()).get() )
{
case 0 :
aFontPos.AdjustX(nLeftBorderSpace );
break;
case 900 :
aFontPos.AdjustY( -nLeftBorderSpace );
break;
case 1800 :
aFontPos.AdjustX( -nLeftBorderSpace );
break;
case 2700 :
aFontPos.AdjustY(nLeftBorderSpace );
break;
}
}
if( aFontPos.X() < 0 )
aFontPos.setX( 0 );
if( aFontPos.Y() < 0 )
aFontPos.setY( 0 );
}
// Handle semi-transparent text if necessary.
std::unique_ptr<SwTransparentTextGuard, o3tl::default_delete<SwTransparentTextGuard>> pTransparentText;
if (m_pFnt->GetColor() != COL_AUTO && m_pFnt->GetColor().IsTransparent())
{
// if drawing to a backend that supports transparency for text color, then we don't need to use this
if (!m_bOnWin || !m_pOut->SupportsOperation(OutDevSupportType::TransparentText) || m_pOut->GetConnectMetaFile())
pTransparentText.reset(new SwTransparentTextGuard(rPor, *this, aDrawInf));
}
if( GetTextFly().IsOn() )
{
// aPos needs to be the TopLeft, because we cannot calculate the
// ClipRects otherwise
const Point aPoint( aFontPos.X(), aFontPos.Y() - rPor.GetAscent() );
const Size aSize( rPor.Width(), rPor.Height() );
aDrawInf.SetPos( aPoint );
aDrawInf.SetSize( aSize );
aDrawInf.SetAscent( rPor.GetAscent() );
aDrawInf.SetKern( bKern ? rPor.Width() : 0 );
aDrawInf.SetWrong( bTmpWrong ? m_pWrongList : nullptr );
aDrawInf.SetGrammarCheck( bTmpGrammarCheck ? m_pGrammarCheckList : nullptr );
aDrawInf.SetSmartTags( bTmpSmart ? m_pSmartTags : nullptr );
GetTextFly().DrawTextOpaque( aDrawInf );
}
else
{
aDrawInf.SetPos( aFontPos );
if( bKern )
m_pFnt->DrawStretchText_( aDrawInf );
else
{
aDrawInf.SetWrong( bTmpWrong ? m_pWrongList : nullptr );
aDrawInf.SetGrammarCheck( bTmpGrammarCheck ? m_pGrammarCheckList : nullptr );
aDrawInf.SetSmartTags( bTmpSmart ? m_pSmartTags : nullptr );
m_pFnt->DrawText_( aDrawInf );
}
}
}
void SwTextPaintInfo::CalcRect( const SwLinePortion& rPor,
SwRect* pRect, SwRect* pIntersect,
const bool bInsideBox ) const
{
Size aSize( rPor.Width(), rPor.Height() );
if( rPor.IsHangingPortion() )
aSize.setWidth( static_cast<const SwHangingPortion&>(rPor).GetInnerWidth() );
if( rPor.InSpaceGrp() && GetSpaceAdd() )
{
SwTwips nAdd = rPor.CalcSpacing( GetSpaceAdd(), *this );
if( rPor.InFieldGrp() && GetSpaceAdd() < 0 && nAdd )
nAdd += GetSpaceAdd() / SPACING_PRECISION_FACTOR;
aSize.AdjustWidth(nAdd );
}
Point aPoint;
if( IsRotated() )
{
tools::Long nTmp = aSize.Width();
aSize.setWidth( aSize.Height() );
aSize.setHeight( nTmp );
if ( 1 == GetDirection() )
{
aPoint.setX( X() - rPor.GetAscent() );
aPoint.setY( Y() - aSize.Height() );
}
else
{
aPoint.setX( X() - rPor.Height() + rPor.GetAscent() );
aPoint.setY( Y() );
}
}
else
{
aPoint.setX( X() );
if (GetTextFrame()->IsVertLR() && !GetTextFrame()->IsVertLRBT())
aPoint.setY( Y() - rPor.Height() + rPor.GetAscent() );
else
aPoint.setY( Y() - rPor.GetAscent() );
}
// Adjust x coordinate if we are inside a bidi portion
const bool bFrameDir = GetTextFrame()->IsRightToLeft();
const bool bCounterDir = ( !bFrameDir && DIR_RIGHT2LEFT == GetDirection() ) ||
( bFrameDir && DIR_LEFT2RIGHT == GetDirection() );
if ( bCounterDir )
aPoint.AdjustX( -(aSize.Width()) );
SwRect aRect( aPoint, aSize );
if ( GetTextFrame()->IsRightToLeft() )
GetTextFrame()->SwitchLTRtoRTL( aRect );
if ( GetTextFrame()->IsVertical() )
GetTextFrame()->SwitchHorizontalToVertical( aRect );
if( bInsideBox && rPor.InTextGrp() )
{
const bool bJoinWithPrev =
static_cast<const SwTextPortion&>(rPor).GetJoinBorderWithPrev();
const bool bJoinWithNext =
static_cast<const SwTextPortion&>(rPor).GetJoinBorderWithNext();
const bool bIsVert = GetTextFrame()->IsVertical();
const bool bIsVertLRBT = GetTextFrame()->IsVertLRBT();
aRect.AddTop( GetFont()->CalcShadowSpace(SvxShadowItemSide::TOP, bIsVert, bIsVertLRBT,
bJoinWithPrev, bJoinWithNext));
aRect.AddBottom( - GetFont()->CalcShadowSpace(SvxShadowItemSide::BOTTOM, bIsVert, bIsVertLRBT,
bJoinWithPrev, bJoinWithNext));
aRect.AddLeft( GetFont()->CalcShadowSpace(SvxShadowItemSide::LEFT, bIsVert, bIsVertLRBT,
bJoinWithPrev, bJoinWithNext));
aRect.AddRight( - GetFont()->CalcShadowSpace(SvxShadowItemSide::RIGHT, bIsVert, bIsVertLRBT,
bJoinWithPrev, bJoinWithNext));
}
if ( pRect )
*pRect = aRect;
if( aRect.HasArea() && pIntersect )
{
::SwAlignRect( aRect, GetVsh(), GetOut() );
if ( GetOut()->IsClipRegion() )
{
SwRect aClip( GetOut()->GetClipRegion().GetBoundRect() );
aRect.Intersection( aClip );
}
*pIntersect = aRect;
}
}
/**
* Draws a special portion
* E.g.: line break portion, tab portion
*
* @param rPor The portion
* @param rRect The rectangle surrounding the character
* @param rCol Specify a color for the character
* @param bCenter Draw the character centered, otherwise left aligned
* @param bRotate Rotate the character if character rotation is set
*/
static void lcl_DrawSpecial( const SwTextPaintInfo& rTextPaintInfo, const SwLinePortion& rPor,
SwRect& rRect, const Color& rCol, sal_Unicode cChar,
sal_uInt8 nOptions )
{
bool bCenter = 0 != ( nOptions & DRAW_SPECIAL_OPTIONS_CENTER );
bool bRotate = 0 != ( nOptions & DRAW_SPECIAL_OPTIONS_ROTATE );
// rRect is given in absolute coordinates
if ( rTextPaintInfo.GetTextFrame()->IsRightToLeft() )
rTextPaintInfo.GetTextFrame()->SwitchRTLtoLTR( rRect );
if ( rTextPaintInfo.GetTextFrame()->IsVertical() )
rTextPaintInfo.GetTextFrame()->SwitchVerticalToHorizontal( rRect );
const SwFont* pOldFnt = rTextPaintInfo.GetFont();
// Font is generated only once:
static SwFont s_aFnt = [&]()
{
SwFont tmp( *pOldFnt );
tmp.SetFamily( FAMILY_DONTKNOW, tmp.GetActual() );
tmp.SetName( numfunc::GetDefBulletFontname(), tmp.GetActual() );
tmp.SetStyleName(OUString(), tmp.GetActual());
tmp.SetCharSet( RTL_TEXTENCODING_SYMBOL, tmp.GetActual() );
return tmp;
}();
// Some of the current values are set at the font:
if ( ! bRotate )
s_aFnt.SetVertical( 0_deg10, rTextPaintInfo.GetTextFrame()->IsVertical() );
else
s_aFnt.SetVertical( pOldFnt->GetOrientation() );
s_aFnt.SetColor(rCol);
Size aFontSize( 0, SPECIAL_FONT_HEIGHT );
s_aFnt.SetSize( aFontSize, s_aFnt.GetActual() );
SwTextPaintInfo& rNonConstTextPaintInfo = const_cast<SwTextPaintInfo&>(rTextPaintInfo);
rNonConstTextPaintInfo.SetFont( &s_aFnt );
// The maximum width depends on the current orientation
const Degree10 nDir = s_aFnt.GetOrientation( rTextPaintInfo.GetTextFrame()->IsVertical() );
SwTwips nMaxWidth;
if (nDir == 900_deg10 || nDir == 2700_deg10)
nMaxWidth = rRect.Height();
else
{
assert(nDir == 0_deg10); //Unknown direction set at font
nMaxWidth = rRect.Width();
}
// check if char fits into rectangle
const OUString aTmp( cChar );
aFontSize = rTextPaintInfo.GetTextSize( aTmp ).SvLSize();
while ( aFontSize.Width() > nMaxWidth )
{
SwTwips nFactor = ( 100 * aFontSize.Width() ) / nMaxWidth;
const SwTwips nOldWidth = aFontSize.Width();
// new height for font
const SwFontScript nAct = s_aFnt.GetActual();
aFontSize.setHeight( ( 100 * s_aFnt.GetSize( nAct ).Height() ) / nFactor );
aFontSize.setWidth( ( 100 * s_aFnt.GetSize( nAct).Width() ) / nFactor );
if ( !aFontSize.Width() && !aFontSize.Height() )
break;
s_aFnt.SetSize( aFontSize, nAct );
aFontSize = rTextPaintInfo.GetTextSize( aTmp ).SvLSize();
if ( aFontSize.Width() >= nOldWidth )
break;
}
const Point aOldPos( rTextPaintInfo.GetPos() );
// adjust values so that tab is vertically and horizontally centered
SwTwips nX = rRect.Left();
SwTwips nY = rRect.Top();
switch ( nDir.get() )
{
case 0 :
if ( bCenter )
nX += ( rRect.Width() - aFontSize.Width() ) / 2;
nY += ( rRect.Height() - aFontSize.Height() ) / 2 + rTextPaintInfo.GetAscent();
break;
case 900 :
if ( bCenter )
nX += ( rRect.Width() - aFontSize.Height() ) / 2 + rTextPaintInfo.GetAscent();
nY += ( rRect.Height() + aFontSize.Width() ) / 2;
break;
case 2700 :
if ( bCenter )
nX += ( rRect.Width() + aFontSize.Height() ) / 2 - rTextPaintInfo.GetAscent();
nY += ( rRect.Height() - aFontSize.Width() ) / 2;
break;
}
Point aTmpPos( nX, nY );
rNonConstTextPaintInfo.SetPos( aTmpPos );
SwTwips nOldWidth = rPor.Width();
const_cast<SwLinePortion&>(rPor).Width(aFontSize.Width());
rTextPaintInfo.DrawText( aTmp, rPor );
const_cast<SwLinePortion&>(rPor).Width( nOldWidth );
rNonConstTextPaintInfo.SetFont( const_cast<SwFont*>(pOldFnt) );
rNonConstTextPaintInfo.SetPos( aOldPos );
}
void SwTextPaintInfo::DrawRect( const SwRect &rRect, bool bRetouche ) const
{
if ( OnWin() || !bRetouche )
{
if( m_aTextFly.IsOn() )
const_cast<SwTextPaintInfo*>(this)->GetTextFly().
DrawFlyRect( m_pOut, rRect );
else
m_pOut->DrawRect( rRect.SVRect() );
}
}
void SwTextPaintInfo::DrawTab( const SwLinePortion &rPor ) const
{
if( !OnWin() )
return;
SwRect aRect;
CalcRect( rPor, &aRect );
if ( ! aRect.HasArea() )
return;
const sal_Unicode cChar = GetTextFrame()->IsRightToLeft() ? CHAR_TAB_RTL : CHAR_TAB;
const sal_uInt8 nOptions = DRAW_SPECIAL_OPTIONS_CENTER | DRAW_SPECIAL_OPTIONS_ROTATE;
lcl_DrawSpecial( *this, rPor, aRect, NON_PRINTING_CHARACTER_COLOR, cChar, nOptions );
}
void SwTextPaintInfo::DrawLineBreak( const SwLinePortion &rPor ) const
{
if( !OnWin() )
return;
SwLineBreakClear eClear = SwLineBreakClear::NONE;
if (rPor.IsBreakPortion())
{
const auto& rBreakPortion = static_cast<const SwBreakPortion&>(rPor);
eClear = rBreakPortion.GetClear();
}
SwTwips nOldWidth = rPor.Width();
const_cast<SwLinePortion&>(rPor).Width( LINE_BREAK_WIDTH );
SwRect aRect;
CalcRect( rPor, &aRect );
if( aRect.HasArea() )
{
const sal_Unicode cChar = GetTextFrame()->IsRightToLeft() ?
CHAR_LINEBREAK_RTL : CHAR_LINEBREAK;
const sal_uInt8 nOptions = 0;
SwRect aTextRect(aRect);
if (eClear == SwLineBreakClear::LEFT || eClear == SwLineBreakClear::ALL)
aTextRect.AddLeft(30);
if (eClear == SwLineBreakClear::RIGHT || eClear == SwLineBreakClear::ALL)
aTextRect.AddRight(-30);
lcl_DrawSpecial( *this, rPor, aTextRect, NON_PRINTING_CHARACTER_COLOR, cChar, nOptions );
if (eClear != SwLineBreakClear::NONE)
{
// Paint indicator if this clear is left/right/all.
m_pOut->Push(vcl::PushFlags::LINECOLOR);
m_pOut->SetLineColor(NON_PRINTING_CHARACTER_COLOR);
if (eClear != SwLineBreakClear::RIGHT)
m_pOut->DrawLine(aRect.BottomLeft(), aRect.TopLeft());
if (eClear != SwLineBreakClear::LEFT)
m_pOut->DrawLine(aRect.BottomRight(), aRect.TopRight());
m_pOut->Pop();
}
}
const_cast<SwLinePortion&>(rPor).Width( nOldWidth );
}
void SwTextPaintInfo::DrawRedArrow( const SwLinePortion &rPor ) const
{
Size aSize( SPECIAL_FONT_HEIGHT, SPECIAL_FONT_HEIGHT );
SwRect aRect( static_cast<const SwArrowPortion&>(rPor).GetPos(), aSize );
sal_Unicode cChar;
if( static_cast<const SwArrowPortion&>(rPor).IsLeft() )
{
aRect.Pos().AdjustY(20 - GetAscent() );
aRect.Pos().AdjustX(20 );
if( aSize.Height() > rPor.Height() )
aRect.Height( rPor.Height() );
cChar = CHAR_LEFT_ARROW;
}
else
{
if( aSize.Height() > rPor.Height() )
aRect.Height( rPor.Height() );
aRect.Pos().AdjustY( -(aRect.Height() + 20) );
aRect.Pos().AdjustX( -(aRect.Width() + 20) );
cChar = CHAR_RIGHT_ARROW;
}
if ( GetTextFrame()->IsVertical() )
GetTextFrame()->SwitchHorizontalToVertical( aRect );
if( aRect.HasArea() )
{
const sal_uInt8 nOptions = 0;
lcl_DrawSpecial( *this, rPor, aRect, COL_LIGHTRED, cChar, nOptions );
}
}
void SwTextPaintInfo::DrawPostIts( bool bScript ) const
{
if( !OnWin() || !m_pOpt->IsPostIts() )
return;
Size aSize;
Point aTmp;
const SwTwips nPostItsWidth = SwViewOption::GetPostItsWidth(GetOut());
const sal_uInt16 nFontHeight = m_pFnt->GetHeight( m_pVsh, *GetOut() );
const sal_uInt16 nFontAscent = m_pFnt->GetAscent( m_pVsh, *GetOut() );
switch ( m_pFnt->GetOrientation( GetTextFrame()->IsVertical() ).get() )
{
case 0 :
aSize.setWidth( nPostItsWidth );
aSize.setHeight( nFontHeight );
aTmp.setX( m_aPos.X() );
aTmp.setY( m_aPos.Y() - nFontAscent );
break;
case 900 :
aSize.setHeight( nPostItsWidth );
aSize.setWidth( nFontHeight );
aTmp.setX( m_aPos.X() - nFontAscent );
aTmp.setY( m_aPos.Y() );
break;
case 2700 :
aSize.setHeight( nPostItsWidth );
aSize.setWidth( nFontHeight );
aTmp.setX( m_aPos.X() - nFontHeight +
nFontAscent );
aTmp.setY( m_aPos.Y() );
break;
}
SwRect aTmpRect( aTmp, aSize );
if ( GetTextFrame()->IsRightToLeft() )
GetTextFrame()->SwitchLTRtoRTL( aTmpRect );
if ( GetTextFrame()->IsVertical() )
GetTextFrame()->SwitchHorizontalToVertical( aTmpRect );
GetOpt().PaintPostIts( const_cast<OutputDevice*>(GetOut()), aTmpRect, bScript );
}
void SwTextPaintInfo::DrawCheckBox(const SwFieldFormCheckboxPortion &rPor, bool bChecked) const
{
SwRect aIntersect;
CalcRect( rPor, &aIntersect );
if ( !aIntersect.HasArea() )
return;
if (OnWin() && GetOpt().IsFieldShadings() &&
!GetOpt().IsPagePreview())
{
OutputDevice* pOut = const_cast<OutputDevice*>(GetOut());
pOut->Push( vcl::PushFlags::LINECOLOR | vcl::PushFlags::FILLCOLOR );
if( m_pFnt->GetHighlightColor() != COL_TRANSPARENT )
pOut->SetFillColor(m_pFnt->GetHighlightColor());
else
pOut->SetFillColor(GetOpt().GetFieldShadingsColor());
pOut->SetLineColor();
pOut->DrawRect( aIntersect.SVRect() );
pOut->Pop();
}
const int delta = 25;
tools::Rectangle r(aIntersect.Left()+delta, aIntersect.Top()+delta, aIntersect.Right()-delta, aIntersect.Bottom()-delta);
m_pOut->Push( vcl::PushFlags::LINECOLOR | vcl::PushFlags::FILLCOLOR );
m_pOut->SetLineColor( Color(0, 0, 0));
m_pOut->SetFillColor();
m_pOut->DrawRect( r );
if (bChecked)
{
m_pOut->DrawLine(r.TopLeft(), r.BottomRight());
m_pOut->DrawLine(r.TopRight(), r.BottomLeft());
}
m_pOut->Pop();
}
void SwTextPaintInfo::DrawBackground( const SwLinePortion &rPor, const Color *pColor ) const
{
OSL_ENSURE( OnWin(), "SwTextPaintInfo::DrawBackground: printer pollution ?" );
SwRect aIntersect;
CalcRect( rPor, nullptr, &aIntersect, true );
if ( !aIntersect.HasArea() )
return;
OutputDevice* pOut = const_cast<OutputDevice*>(GetOut());
pOut->Push( vcl::PushFlags::LINECOLOR | vcl::PushFlags::FILLCOLOR );
if ( pColor )
pOut->SetFillColor( *pColor );
else
pOut->SetFillColor( GetOpt().GetFieldShadingsColor() );
pOut->SetLineColor();
DrawRect( aIntersect, true );
pOut->Pop();
}
void SwTextPaintInfo::DrawBackBrush( const SwLinePortion &rPor ) const
{
{
SwRect aIntersect;
CalcRect( rPor, &aIntersect, nullptr, true );
if(aIntersect.HasArea())
{
SwPosition const aPosition(m_pFrame->MapViewToModelPos(GetIdx()));
const ::sw::mark::IMark* pFieldmark =
m_pFrame->GetDoc().getIDocumentMarkAccess()->getInnerFieldmarkFor(aPosition);
bool bIsStartMark = (TextFrameIndex(1) == GetLen()
&& CH_TXT_ATR_FIELDSTART == GetText()[sal_Int32(GetIdx())]);
if(pFieldmark) {
SAL_INFO("sw.core", "Found Fieldmark " << pFieldmark->ToString());
}
if(bIsStartMark)
SAL_INFO("sw.core", "Found StartMark");
if (OnWin() && (pFieldmark!=nullptr || bIsStartMark) &&
GetOpt().IsFieldShadings() &&
!GetOpt().IsPagePreview())
{
OutputDevice* pOutDev = const_cast<OutputDevice*>(GetOut());
pOutDev->Push( vcl::PushFlags::LINECOLOR | vcl::PushFlags::FILLCOLOR );
pOutDev->SetFillColor( GetOpt().GetFieldShadingsColor() );
pOutDev->SetLineColor( );
pOutDev->DrawRect( aIntersect.SVRect() );
pOutDev->Pop();
}
}
}
SwRect aIntersect;
CalcRect( rPor, nullptr, &aIntersect, true );
if ( !aIntersect.HasArea() )
return;
OutputDevice* pTmpOut = const_cast<OutputDevice*>(GetOut());
// #i16816# tagged pdf support
SwTaggedPDFHelper aTaggedPDFHelper( nullptr, nullptr, nullptr, *pTmpOut );
Color aFillColor;
if( m_pFnt->GetHighlightColor() != COL_TRANSPARENT )
{
aFillColor = m_pFnt->GetHighlightColor();
}
else
{
if( !m_pFnt->GetBackColor() )
return;
aFillColor = *m_pFnt->GetBackColor();
}
pTmpOut->Push( vcl::PushFlags::LINECOLOR | vcl::PushFlags::FILLCOLOR );
pTmpOut->SetFillColor(aFillColor);
pTmpOut->SetLineColor();
DrawRect( aIntersect, false );
pTmpOut->Pop();
}
void SwTextPaintInfo::DrawBorder( const SwLinePortion &rPor ) const
{
SwRect aDrawArea;
CalcRect( rPor, &aDrawArea );
if ( aDrawArea.HasArea() )
{
PaintCharacterBorder(*m_pFnt, aDrawArea, GetTextFrame()->IsVertical(),
GetTextFrame()->IsVertLRBT(), rPor.GetJoinBorderWithPrev(),
rPor.GetJoinBorderWithNext());
}
}
namespace {
bool HasValidPropertyValue(const uno::Any& rAny)
{
if (bool bValue; rAny >>= bValue)
{
return true;
}
else if (OUString aValue; (rAny >>= aValue) && !(aValue.isEmpty()))
{
return true;
}
else if (awt::FontSlant eValue; rAny >>= eValue)
{
return true;
}
else if (tools::Long nValueLong; rAny >>= nValueLong)
{
return true;
}
else if (double fValue; rAny >>= fValue)
{
return true;
}
else if (short nValueShort; rAny >>= nValueShort)
{
return true;
}
else
return false;
}
}
void SwTextPaintInfo::DrawCSDFHighlighting(const SwLinePortion &rPor) const
{
// Don't use GetActiveView() as it does not work as expected when there are multiple open
// documents.
SwView* pView = SwTextFrame::GetView();
if (!pView)
return;
StylesHighlighterColorMap& rCharStylesColorMap = pView->GetStylesHighlighterCharColorMap();
if (rCharStylesColorMap.empty() && !pView->IsHighlightCharDF())
return;
SwRect aRect;
CalcRect(rPor, &aRect, nullptr, true);
if(!aRect.HasArea())
return;
SwTextFrame* pFrame = const_cast<SwTextFrame*>(GetTextFrame());
if (!pFrame)
return;
SwPosition aPosition(pFrame->MapViewToModelPos(GetIdx()));
SwPosition aMarkPosition(pFrame->MapViewToModelPos(GetIdx() + GetLen()));
rtl::Reference<SwXTextRange> xRange(
SwXTextRange::CreateXTextRange(pFrame->GetDoc(), aPosition, &aMarkPosition));
OUString sCurrentCharStyle;
xRange->getPropertyValue(u"CharStyleName"_ustr) >>= sCurrentCharStyle;
std::optional<OUString> sCSNumberOrDF; // CS number or "df" or not used
std::optional<Color> aFillColor;
// check for CS formatting, if not CS formatted check for direct character formatting
if (!sCurrentCharStyle.isEmpty())
{
if (!rCharStylesColorMap.empty())
{
OUString sCharStyleDisplayName;
sCharStyleDisplayName = SwStyleNameMapper::GetUIName(sCurrentCharStyle,
SwGetPoolIdFromName::ChrFmt);
if (!sCharStyleDisplayName.isEmpty()
&& rCharStylesColorMap.find(sCharStyleDisplayName)
!= rCharStylesColorMap.end())
{
aFillColor = rCharStylesColorMap[sCharStyleDisplayName].first;
sCSNumberOrDF = OUString::number(rCharStylesColorMap[sCharStyleDisplayName].second);
}
}
}
// not character style formatted
else if (pView->IsHighlightCharDF())
{
const std::vector<OUString> aHiddenProperties{ UNO_NAME_RSID,
UNO_NAME_PARA_IS_NUMBERING_RESTART,
UNO_NAME_PARA_STYLE_NAME,
UNO_NAME_PARA_CONDITIONAL_STYLE_NAME,
UNO_NAME_PAGE_STYLE_NAME,
UNO_NAME_NUMBERING_START_VALUE,
UNO_NAME_NUMBERING_IS_NUMBER,
UNO_NAME_PARA_CONTINUEING_PREVIOUS_SUB_TREE,
UNO_NAME_CHAR_STYLE_NAME,
UNO_NAME_NUMBERING_LEVEL,
UNO_NAME_SORTED_TEXT_ID,
UNO_NAME_PARRSID,
UNO_NAME_CHAR_COLOR_THEME,
UNO_NAME_CHAR_COLOR_TINT_OR_SHADE };
SfxItemPropertySet const& rPropSet(
*aSwMapProvider.GetPropertySet(PROPERTY_MAP_CHAR_AUTO_STYLE));
SfxItemPropertyMap const& rMap(rPropSet.getPropertyMap());
const uno::Sequence<beans::Property> aProperties
= xRange->getPropertySetInfo()->getProperties();
for (const beans::Property& rProperty : aProperties)
{
const OUString& rPropName = rProperty.Name;
if (!rMap.hasPropertyByName(rPropName))
continue;
if (std::find(aHiddenProperties.begin(), aHiddenProperties.end(), rPropName)
!= aHiddenProperties.end())
continue;
if (xRange->getPropertyState(rPropName) == beans::PropertyState_DIRECT_VALUE)
{
const uno::Any aAny = xRange->getPropertyValue(rPropName);
if (HasValidPropertyValue(aAny))
{
sCSNumberOrDF = SwResId(STR_CHARACTER_DIRECT_FORMATTING_TAG);
aFillColor = COL_LIGHTGRAY;
break;
}
}
}
}
if (sCSNumberOrDF)
{
OutputDevice* pTmpOut = const_cast<OutputDevice*>(GetOut());
pTmpOut->Push(vcl::PushFlags::LINECOLOR | vcl::PushFlags::FILLCOLOR
| vcl::PushFlags::TEXTLAYOUTMODE | vcl::PushFlags::FONT);
// draw a filled rectangle at the formatted CS or DF text
pTmpOut->SetFillColor(aFillColor.value());
pTmpOut->SetLineColor(aFillColor.value());
tools::Rectangle aSVRect(aRect.SVRect());
pTmpOut->DrawRect(aSVRect);
// calculate size and position for the CS number or "df" text and rectangle
tools::Long nWidth = pTmpOut->GetTextWidth(sCSNumberOrDF.value());
tools::Long nHeight = pTmpOut->GetTextHeight();
aSVRect.SetSize(Size(nWidth, nHeight));
aSVRect.Move(-(nWidth / 1.5), -(nHeight / 1.5));
vcl::Font aFont(pTmpOut->GetFont());
aFont.SetOrientation(Degree10(0));
pTmpOut->SetFont(aFont);
pTmpOut->SetLayoutMode(vcl::text::ComplexTextLayoutFlags::TextOriginLeft);
//pTmpOut->SetLayoutMode(vcl::text::ComplexTextLayoutFlags::BiDiStrong);
pTmpOut->SetTextFillColor(aFillColor.value());
pTmpOut->DrawText(aSVRect, sCSNumberOrDF.value(), DrawTextFlags::NONE);
pTmpOut->Pop();
}
}
void SwTextPaintInfo::DrawViewOpt( const SwLinePortion &rPor,
PortionType nWhich, const Color *pColor ) const
{
if( !OnWin() || IsMulti() )
return;
bool bDraw = false;
if ( !GetOpt().IsPagePreview()
&& !GetOpt().IsReadonly() )
{
switch( nWhich )
{
case PortionType::Tab:
if ( GetOpt().IsViewMetaChars() )
bDraw = GetOpt().IsTab();
break;
case PortionType::SoftHyphen:
if ( GetOpt().IsViewMetaChars() )
bDraw = GetOpt().IsSoftHyph();
break;
case PortionType::Blank:
if ( GetOpt().IsViewMetaChars() )
bDraw = GetOpt().IsHardBlank();
break;
case PortionType::ControlChar:
if ( GetOpt().IsViewMetaChars() )
bDraw = true;
break;
case PortionType::Bookmark:
// no shading
break;
case PortionType::Footnote:
case PortionType::QuoVadis:
case PortionType::Number:
case PortionType::Hidden:
case PortionType::Tox:
case PortionType::Ref:
case PortionType::Meta:
case PortionType::ContentControl:
case PortionType::Field:
case PortionType::InputField:
// input field shading also in read-only mode
if (GetOpt().IsFieldShadings()
&& ( PortionType::Number != nWhich
|| m_pFrame->GetTextNodeForParaProps()->HasMarkedLabel())) // #i27615#
{
bDraw = PortionType::Footnote != nWhich || m_pFrame->IsFootnoteAllowed();
}
break;
default:
{
OSL_ENSURE( false, "SwTextPaintInfo::DrawViewOpt: don't know how to draw this" );
break;
}
}
}
if ( bDraw )
DrawBackground( rPor, pColor );
}
void SwTextPaintInfo::NotifyURL_(const SwLinePortion& rPor) const
{
assert(pNoteURL);
SwRect aIntersect;
CalcRect(rPor, nullptr, &aIntersect);
if (aIntersect.HasArea())
{
SwTextNode* pNd = const_cast<SwTextNode*>(GetTextFrame()->GetTextNodeFirst());
SwTextAttr* const pAttr = pNd->GetTextAttrAt(sal_Int32(GetIdx()), RES_TXTATR_INETFMT);
if (pAttr)
{
const SwFormatINetFormat& rFormat = pAttr->GetINetFormat();
pNoteURL->InsertURLNote(rFormat.GetValue(), rFormat.GetTargetFrame(), aIntersect);
}
else if (rPor.IsFlyCntPortion())
{
if (auto* pFlyContentPortion = dynamic_cast<const sw::FlyContentPortion*>(&rPor))
{
if (auto* pFlyFtame = pFlyContentPortion->GetFlyFrame())
{
if (auto* pFormat = pFlyFtame->GetFormat())
{
auto& url = pFormat->GetURL(); // TODO: url.GetMap() ?
pNoteURL->InsertURLNote(url.GetURL(), url.GetTargetFrameName(), aIntersect);
}
}
}
}
}
}
static void lcl_InitHyphValues( PropertyValues &rVals,
sal_Int16 nMinLeading, sal_Int16 nMinTrailing,
bool bNoCapsHyphenation, bool bNoLastWordHyphenation,
sal_Int16 nMinWordLength, sal_Int16 nTextHyphZone, bool bKeep, sal_Int16 nKeepType,
sal_Int16 nCompoundMinLeading )
{
sal_Int32 nLen = rVals.getLength();
if (0 == nLen) // yet to be initialized?
{
rVals.realloc( 9 );
PropertyValue *pVal = rVals.getArray();
pVal[0].Name = UPN_HYPH_MIN_LEADING;
pVal[0].Handle = UPH_HYPH_MIN_LEADING;
pVal[0].Value <<= nMinLeading;
pVal[1].Name = UPN_HYPH_MIN_TRAILING;
pVal[1].Handle = UPH_HYPH_MIN_TRAILING;
pVal[1].Value <<= nMinTrailing;
pVal[2].Name = UPN_HYPH_NO_CAPS;
pVal[2].Handle = UPH_HYPH_NO_CAPS;
pVal[2].Value <<= bNoCapsHyphenation;
pVal[3].Name = UPN_HYPH_NO_LAST_WORD;
pVal[3].Handle = UPH_HYPH_NO_LAST_WORD;
pVal[3].Value <<= bNoLastWordHyphenation;
pVal[4].Name = UPN_HYPH_MIN_WORD_LENGTH;
pVal[4].Handle = UPH_HYPH_MIN_WORD_LENGTH;
pVal[4].Value <<= nMinWordLength;
pVal[5].Name = UPN_HYPH_ZONE;
pVal[5].Handle = UPH_HYPH_ZONE;
pVal[5].Value <<= nTextHyphZone;
pVal[6].Name = UPN_HYPH_KEEP_TYPE;
pVal[6].Handle = UPH_HYPH_KEEP_TYPE;
pVal[6].Value <<= nKeepType;
pVal[7].Name = UPN_HYPH_COMPOUND_MIN_LEADING;
pVal[7].Handle = UPH_HYPH_COMPOUND_MIN_LEADING;
pVal[7].Value <<= nCompoundMinLeading;
pVal[8].Name = UPN_HYPH_KEEP;
pVal[8].Handle = UPH_HYPH_KEEP;
pVal[8].Value <<= bKeep;
}
else if (9 == nLen) // already initialized once?
{
PropertyValue *pVal = rVals.getArray();
pVal[0].Value <<= nMinLeading;
pVal[1].Value <<= nMinTrailing;
pVal[2].Value <<= bNoCapsHyphenation;
pVal[3].Value <<= bNoLastWordHyphenation;
pVal[4].Value <<= nMinWordLength;
pVal[5].Value <<= nTextHyphZone;
pVal[6].Value <<= nKeepType;
pVal[7].Value <<= nCompoundMinLeading;
pVal[8].Value <<= bKeep;
}
else {
OSL_FAIL( "unexpected size of sequence" );
}
}
const PropertyValues & SwTextFormatInfo::GetHyphValues() const
{
OSL_ENSURE( 9 == m_aHyphVals.getLength(),
"hyphenation values not yet initialized" );
return m_aHyphVals;
}
bool SwTextFormatInfo::InitHyph( const bool bAutoHyphen )
{
const SwAttrSet& rAttrSet = GetTextFrame()->GetTextNodeForParaProps()->GetSwAttrSet();
SetHanging( rAttrSet.GetHangingPunctuation().GetValue() );
SetScriptSpace( rAttrSet.GetScriptSpace().GetValue() );
SetForbiddenChars( rAttrSet.GetForbiddenRule().GetValue() );
const SvxHyphenZoneItem &rAttr = rAttrSet.GetHyphenZone();
MaxHyph() = rAttr.GetMaxHyphens();
const bool bAuto = bAutoHyphen || rAttr.IsHyphen();
if( bAuto || m_bInterHyph )
{
const sal_Int16 nMinimalLeading = std::max(rAttr.GetMinLead(), sal_uInt8(2));
const sal_Int16 nMinimalTrailing = rAttr.GetMinTrail();
const sal_Int16 nMinimalWordLength = rAttr.GetMinWordLength();
const bool bNoCapsHyphenation = rAttr.IsNoCapsHyphenation();
const bool bNoLastWordHyphenation = rAttr.IsNoLastWordHyphenation();
const sal_Int16 nTextHyphZone = rAttr.GetTextHyphenZone();
const bool bKeep = rAttr.IsKeep();
const sal_Int16 nKeepType = rAttr.GetKeepType();
const sal_Int16 nCompoundMinimalLeading = std::max(rAttr.GetCompoundMinLead(), sal_uInt8(2));
lcl_InitHyphValues( m_aHyphVals, nMinimalLeading, nMinimalTrailing,
bNoCapsHyphenation, bNoLastWordHyphenation,
nMinimalWordLength, nTextHyphZone, bKeep, nKeepType, nCompoundMinimalLeading );
}
return bAuto;
}
void SwTextFormatInfo::CtorInitTextFormatInfo( OutputDevice* pRenderContext, SwTextFrame *pNewFrame, const bool bNewInterHyph,
const bool bNewQuick, const bool bTst )
{
CtorInitTextPaintInfo( pRenderContext, pNewFrame, SwRect() );
m_bQuick = bNewQuick;
m_bInterHyph = bNewInterHyph;
//! needs to be done in this order
m_bAutoHyph = InitHyph();
m_bIgnoreFly = false;
m_bFakeLineStart = false;
m_bShift = false;
m_bDropInit = false;
m_bTestFormat = bTst;
m_nLeft = 0;
m_nRight = 0;
m_nFirst = 0;
m_nRealWidth = 0;
m_nForcedLeftMargin = 0;
m_nExtraAscent = 0;
m_nExtraDescent = 0;
m_pRest = nullptr;
m_nLineHeight = 0;
m_nLineNetHeight = 0;
SetLineStart(TextFrameIndex(0));
SvtCTLOptions::TextNumerals const nTextNumerals(
SvtCTLOptions::GetCTLTextNumerals());
// cannot cache for NUMERALS_CONTEXT because we need to know the string
// for the whole paragraph now
if (nTextNumerals != SvtCTLOptions::NUMERALS_CONTEXT)
{
// set digit mode to what will be used later to get same results
SwDigitModeModifier const m(*m_pRef, LANGUAGE_NONE /*dummy*/);
assert(m_pRef->GetDigitLanguage() != LANGUAGE_NONE);
SetCachedVclData(OutputDevice::CreateTextLayoutCache(*m_pText));
}
Init();
}
/**
* If the Hyphenator returns ERROR or the language is set to NOLANGUAGE
* we do not hyphenate.
* Else, we always hyphenate if we do interactive hyphenation.
* If we do not do interactive hyphenation, we only hyphenate if ParaFormat is
* set to automatic hyphenation.
*/
bool SwTextFormatInfo::IsHyphenate() const
{
if( !m_bInterHyph && !m_bAutoHyph )
return false;
LanguageType eTmp = GetFont()->GetLanguage();
// TODO: check for more ideographic langs w/o hyphenation as a concept
if ( LANGUAGE_DONTKNOW == eTmp || LANGUAGE_NONE == eTmp
|| !MsLangId::usesHyphenation(eTmp) )
return false;
uno::Reference< XHyphenator > xHyph = ::GetHyphenator();
if (!xHyph.is())
return false;
if (m_bInterHyph)
SvxSpellWrapper::CheckHyphLang( xHyph, eTmp );
if (!xHyph->hasLocale(g_pBreakIt->GetLocale(eTmp)))
{
SfxObjectShell* pShell = m_pFrame->GetDoc().GetDocShell();
if (pShell)
{
pShell->AppendInfoBarWhenReady(
u"hyphenationmissing"_ustr, SwResId(STR_HYPH_MISSING),
SwResId(STR_HYPH_MISSING_DETAIL)
.replaceFirst("%1", LanguageTag::convertToBcp47( g_pBreakIt->GetLocale(eTmp))),
InfobarType::WARNING);
}
}
return xHyph->hasLocale( g_pBreakIt->GetLocale(eTmp) );
}
const SwFormatDrop *SwTextFormatInfo::GetDropFormat() const
{
const SwFormatDrop *pDrop = &GetTextFrame()->GetTextNodeForParaProps()->GetSwAttrSet().GetDrop();
if( 1 >= pDrop->GetLines() ||
( !pDrop->GetChars() && !pDrop->GetWholeWord() ) )
pDrop = nullptr;
return pDrop;
}
void SwTextFormatInfo::Init()
{
// Not initialized: pRest, nLeft, nRight, nFirst, nRealWidth
X(0);
m_bArrowDone = m_bFull = m_bFootnoteDone = m_bErgoDone = m_bNumDone = m_bNoEndHyph =
m_bNoMidHyph = m_bStop = m_bNewLine = m_bUnderflow = m_bTabOverflow = false;
// generally we do not allow number portions in follows, except...
if ( GetTextFrame()->IsFollow() )
{
const SwTextFrame* pMaster = GetTextFrame()->FindMaster();
OSL_ENSURE(pMaster, "pTextFrame without Master");
const SwLinePortion* pTmpPara = pMaster ? pMaster->GetPara() : nullptr;
// there is a master for this follow and the master does not have
// any contents (especially it does not have a number portion)
m_bNumDone = ! pTmpPara ||
! static_cast<const SwParaPortion*>(pTmpPara)->GetFirstPortion()->IsFlyPortion();
}
m_pRoot = nullptr;
m_pLast = nullptr;
m_pFly = nullptr;
m_pLastTab = nullptr;
m_pUnderflow = nullptr;
m_cTabDecimal = 0;
m_nWidth = m_nRealWidth;
m_nForcedLeftMargin = 0;
m_nExtraAscent = 0;
m_nExtraDescent = 0;
m_nSoftHyphPos = TextFrameIndex(0);
m_nLastBookmarkPos = TextFrameIndex(-1);
m_cHookChar = 0;
SetIdx(TextFrameIndex(0));
SetLen(TextFrameIndex(GetText().getLength()));
SetPaintOfst(0);
}
SwTextFormatInfo::SwTextFormatInfo(OutputDevice* pRenderContext, SwTextFrame *pFrame, const bool bInterHyphL,
const bool bQuickL, const bool bTst)
{
CtorInitTextFormatInfo(pRenderContext, pFrame, bInterHyphL, bQuickL, bTst);
}
/**
* There are a few differences between a copy constructor
* and the following constructor for multi-line formatting.
* The root is the first line inside the multi-portion,
* the line start is the actual position in the text,
* the line width is the rest width from the surrounding line
* and the bMulti and bFirstMulti-flag has to be set correctly.
*/
SwTextFormatInfo::SwTextFormatInfo( const SwTextFormatInfo& rInf,
SwLineLayout& rLay, SwTwips nActWidth ) :
SwTextPaintInfo( rInf ),
m_pRoot(&rLay),
m_pLast(&rLay),
m_pFly(nullptr),
m_pUnderflow(nullptr),
m_pRest(nullptr),
m_pLastTab(nullptr),
m_nSoftHyphPos(TextFrameIndex(0)),
m_nLineStart(rInf.GetIdx()),
m_nLeft(rInf.m_nLeft),
m_nRight(rInf.m_nRight),
m_nFirst(rInf.m_nLeft),
m_nRealWidth(nActWidth),
m_nWidth(m_nRealWidth),
m_nLineHeight(0),
m_nLineNetHeight(0),
m_nForcedLeftMargin(0),
m_nExtraAscent(0),
m_nExtraDescent(0),
m_bFull(false),
m_bFootnoteDone(true),
m_bErgoDone(true),
m_bNumDone(true),
m_bArrowDone(true),
m_bStop(false),
m_bNewLine(true),
m_bShift(false),
m_bUnderflow(false),
m_bInterHyph(false),
m_bAutoHyph(false),
m_bDropInit(false),
m_bQuick(rInf.m_bQuick),
m_bNoEndHyph(false),
m_bNoMidHyph(false),
m_bIgnoreFly(false),
m_bFakeLineStart(false),
m_bTabOverflow( false ),
m_bTestFormat(rInf.m_bTestFormat),
m_cTabDecimal(0),
m_cHookChar(0),
m_nMaxHyph(0)
{
SetMulti( true );
SetFirstMulti( rInf.IsFirstMulti() );
}
void SwTextFormatInfo::UpdateTabSeen(PortionType type)
{
switch (type)
{
case PortionType::TabLeft:
m_eLastTabsSeen = TabSeen::Left;
break;
case PortionType::TabRight:
m_eLastTabsSeen = TabSeen::Right;
break;
case PortionType::TabCenter:
m_eLastTabsSeen = TabSeen::Center;
break;
case PortionType::TabDecimal:
m_eLastTabsSeen = TabSeen::Decimal;
break;
case PortionType::Break:
m_eLastTabsSeen = TabSeen::None;
break;
default:
break;
}
}
void SwTextFormatInfo::SetLast(SwLinePortion* pNewLast)
{
m_pLast = pNewLast;
assert(pNewLast); // We never pass nullptr here. If we start, then a check is needed below.
UpdateTabSeen(pNewLast->GetWhichPor());
}
bool SwTextFormatInfo::CheckFootnotePortion_( SwLineLayout const * pCurr )
{
const SwTwips nHeight = pCurr->GetRealHeight();
for( SwLinePortion *pPor = pCurr->GetNextPortion(); pPor; pPor = pPor->GetNextPortion() )
{
if( pPor->IsFootnotePortion() && nHeight > static_cast<SwFootnotePortion*>(pPor)->Orig() )
{
SetLineHeight( nHeight );
SetLineNetHeight( pCurr->Height() );
return true;
}
}
return false;
}
TextFrameIndex SwTextFormatInfo::ScanPortionEnd(TextFrameIndex const nStart,
TextFrameIndex const nEnd)
{
m_cHookChar = 0;
TextFrameIndex i = nStart;
// Used for decimal tab handling:
const sal_Unicode cTabDec = GetLastTab() ? GetTabDecimal() : 0;
const sal_Unicode cThousandSep = ',' == cTabDec ? '.' : ',';
// #i45951# German (Switzerland) uses ' as thousand separator
const sal_Unicode cThousandSep2 = ',' == cTabDec ? '.' : '\'';
bool bNumFound = false;
const bool bTabCompat = GetTextFrame()->GetDoc().getIDocumentSettingAccess().get(DocumentSettingId::TAB_COMPAT);
for( ; i < nEnd; ++i )
{
const sal_Unicode cPos = GetChar( i );
switch( cPos )
{
case CH_TXTATR_BREAKWORD:
case CH_TXTATR_INWORD:
if( !HasHint( i ))
break;
[[fallthrough]];
case CHAR_SOFTHYPHEN:
case CHAR_HARDHYPHEN:
case CHAR_HARDBLANK:
case CH_TAB:
case CH_BREAK:
case CHAR_ZWSP :
case CHAR_WJ :
m_cHookChar = cPos;
return i;
default:
if ( cTabDec )
{
if( cTabDec == cPos )
{
OSL_ENSURE( cPos, "Unexpected end of string" );
if( cPos ) // robust
{
m_cHookChar = cPos;
return i;
}
}
// Compatibility: First non-digit character behind a
// a digit character becomes the hook character
if ( bTabCompat )
{
if ( ( 0x2F < cPos && cPos < 0x3A ) ||
( bNumFound && ( cPos == cThousandSep || cPos == cThousandSep2 ) ) )
{
bNumFound = true;
}
else
{
if ( bNumFound )
{
m_cHookChar = cPos;
SetTabDecimal( cPos );
return i;
}
}
}
}
}
}
// Check if character *behind* the portion has
// to become the hook:
if (i == nEnd && i < TextFrameIndex(GetText().getLength()) && bNumFound)
{
const sal_Unicode cPos = GetChar( i );
if ( cPos != cTabDec && cPos != cThousandSep && cPos !=cThousandSep2 && ( 0x2F >= cPos || cPos >= 0x3A ) )
{
m_cHookChar = GetChar( i );
SetTabDecimal( m_cHookChar );
}
}
return i;
}
bool SwTextFormatInfo::LastKernPortion()
{
if( GetLast() )
{
if( GetLast()->IsKernPortion() )
return true;
if( GetLast()->Width() || ( GetLast()->GetLen() &&
!GetLast()->IsHolePortion() ) )
return false;
}
SwLinePortion* pPor = GetRoot();
SwLinePortion *pKern = nullptr;
while( pPor )
{
if( pPor->IsKernPortion() )
pKern = pPor;
else if( pPor->Width() || ( pPor->GetLen() && !pPor->IsHolePortion() ) )
pKern = nullptr;
pPor = pPor->GetNextPortion();
}
if( pKern )
{
SetLast( pKern );
return true;
}
return false;
}
SwTwips SwTextFormatInfo::GetLineWidth()
{
SwTwips nLineWidth = Width() - X();
const bool bTabOverMargin = GetTextFrame()->GetDoc().getIDocumentSettingAccess().get(
DocumentSettingId::TAB_OVER_MARGIN);
const bool bTabOverSpacing = GetTextFrame()->GetDoc().getIDocumentSettingAccess().get(
DocumentSettingId::TAB_OVER_SPACING);
if (!bTabOverMargin && !bTabOverSpacing)
return nLineWidth;
SwTabPortion* pLastTab = GetLastTab();
if (!pLastTab)
return nLineWidth;
// Consider tab portions over the printing bounds of the text frame.
if (pLastTab->GetTabPos() <= Width())
return nLineWidth;
// Calculate the width that starts at the left (or in case of first line:
// first) margin, but ends after the right paragraph margin:
//
// +--------------------+
// |LL| |RR|
// +--------------------+
// ^ m_nLeftMargin (absolute)
// ^ nLeftMarginWidth (relative to m_nLeftMargin), X() is relative to this
// ^ right margin
// ^ paragraph right
// <--------------------> is GetTextFrame()->getFrameArea().Width()
// <--------------> is Width()
// <-----------------> is what we need to be able to compare to X() (nTextFrameWidth)
SwTwips nLeftMarginWidth = m_nLeftMargin - GetTextFrame()->getFrameArea().Left();
SwTwips nTextFrameWidth = GetTextFrame()->getFrameArea().Width() - nLeftMarginWidth;
// If there is one such tab portion, then text is allowed to use the full
// text frame area to the right (RR above, but not LL).
nLineWidth = nTextFrameWidth - X();
if (!bTabOverMargin) // thus bTabOverSpacing only
{
// right, center, decimal can back-fill all the available space - same as TabOverMargin
if (pLastTab->GetWhichPor() == PortionType::TabLeft)
nLineWidth = nTextFrameWidth - pLastTab->GetTabPos();
}
else
{ // tdf#158658 Put content after tab into margin like Word.
// Try to limit the paragraph to 55.87cm, it's max tab pos in Word UI.
nLineWidth = o3tl::toTwips(558, o3tl::Length::mm) - X();
}
return nLineWidth;
}
SwTextSlot::SwTextSlot(
const SwTextSizeInfo *pNew,
const SwLinePortion *pPor,
bool bTextLen,
bool bExgLists,
OUString const & rCh )
: pOldText(nullptr)
, m_pOldSmartTagList(nullptr)
, m_pOldGrammarCheckList(nullptr)
, nIdx(0)
, nLen(0)
, nMeasureLen(0)
, pInf(nullptr)
{
if( rCh.isEmpty() )
{
bOn = pPor->GetExpText( *pNew, aText );
}
else
{
aText = rCh;
bOn = true;
}
// The text is replaced ...
if( !bOn )
return;
pInf = const_cast<SwTextSizeInfo*>(pNew);
nIdx = pInf->GetIdx();
nLen = pInf->GetLen();
nMeasureLen = pInf->GetMeasureLen();
pOldText = &(pInf->GetText());
m_pOldCachedVclData = pInf->GetCachedVclData();
pInf->SetText( aText );
pInf->SetIdx(TextFrameIndex(0));
pInf->SetLen(bTextLen ? TextFrameIndex(pInf->GetText().getLength()) : pPor->GetLen());
if (nMeasureLen != TextFrameIndex(COMPLETE_STRING))
pInf->SetMeasureLen(TextFrameIndex(COMPLETE_STRING));
pInf->SetCachedVclData(nullptr);
// ST2
if ( !bExgLists )
return;
m_pOldSmartTagList = static_cast<SwTextPaintInfo*>(pInf)->GetSmartTags();
if (m_pOldSmartTagList)
{
std::pair<SwTextNode const*, sal_Int32> pos(pNew->GetTextFrame()->MapViewToModel(nIdx));
SwWrongList const*const pSmartTags(pos.first->GetSmartTags());
if (pSmartTags)
{
const sal_uInt16 nPos = pSmartTags->GetWrongPos(pos.second);
const sal_Int32 nListPos = pSmartTags->Pos(nPos);
if (nListPos == pos.second && pSmartTags->SubList(nPos) != nullptr)
{
m_pTempIter.reset(new sw::WrongListIterator(*pSmartTags->SubList(nPos)));
static_cast<SwTextPaintInfo*>(pInf)->SetSmartTags(m_pTempIter.get());
}
else if (!m_pTempList && nPos < pSmartTags->Count()
&& nListPos < pos.second && !aText.isEmpty())
{
m_pTempList.reset(new SwWrongList( WRONGLIST_SMARTTAG ));
m_pTempList->Insert( OUString(), nullptr, 0, aText.getLength(), 0 );
m_pTempIter.reset(new sw::WrongListIterator(*m_pTempList));
static_cast<SwTextPaintInfo*>(pInf)->SetSmartTags(m_pTempIter.get());
}
else
static_cast<SwTextPaintInfo*>(pInf)->SetSmartTags(nullptr);
}
else
static_cast<SwTextPaintInfo*>(pInf)->SetSmartTags(nullptr);
}
m_pOldGrammarCheckList = static_cast<SwTextPaintInfo*>(pInf)->GetGrammarCheckList();
if (!m_pOldGrammarCheckList)
return;
std::pair<SwTextNode const*, sal_Int32> pos(pNew->GetTextFrame()->MapViewToModel(nIdx));
SwWrongList const*const pGrammar(pos.first->GetGrammarCheck());
if (pGrammar)
{
const sal_uInt16 nPos = pGrammar->GetWrongPos(pos.second);
const sal_Int32 nListPos = pGrammar->Pos(nPos);
if (nListPos == pos.second && pGrammar->SubList(nPos) != nullptr)
{
m_pTempIter.reset(new sw::WrongListIterator(*pGrammar->SubList(nPos)));
static_cast<SwTextPaintInfo*>(pInf)->SetGrammarCheckList(m_pTempIter.get());
}
else if (!m_pTempList && nPos < pGrammar->Count()
&& nListPos < pos.second && !aText.isEmpty())
{
m_pTempList.reset(new SwWrongList( WRONGLIST_GRAMMAR ));
m_pTempList->Insert( OUString(), nullptr, 0, aText.getLength(), 0 );
m_pTempIter.reset(new sw::WrongListIterator(*m_pTempList));
static_cast<SwTextPaintInfo*>(pInf)->SetGrammarCheckList(m_pTempIter.get());
}
else
static_cast<SwTextPaintInfo*>(pInf)->SetGrammarCheckList(nullptr);
}
else
static_cast<SwTextPaintInfo*>(pInf)->SetGrammarCheckList(nullptr);
}
SwTextSlot::~SwTextSlot()
{
if( !bOn )
return;
pInf->SetCachedVclData(m_pOldCachedVclData);
pInf->SetText( *pOldText );
pInf->SetIdx( nIdx );
pInf->SetLen( nLen );
pInf->SetMeasureLen( nMeasureLen );
// ST2
// Restore old smart tag list
if (m_pOldSmartTagList)
static_cast<SwTextPaintInfo*>(pInf)->SetSmartTags(m_pOldSmartTagList);
if (m_pOldGrammarCheckList)
static_cast<SwTextPaintInfo*>(pInf)->SetGrammarCheckList(m_pOldGrammarCheckList);
}
SwFontSave::SwFontSave(const SwTextSizeInfo &rInf, SwFont *pNew,
SwAttrIter* pItr)
: pInf(nullptr)
, pFnt(pNew ? const_cast<SwTextSizeInfo&>(rInf).GetFont() : nullptr)
, pIter(nullptr)
{
if( !pFnt )
return;
pInf = &const_cast<SwTextSizeInfo&>(rInf);
// In these cases we temporarily switch to the new font:
// 1. the fonts have a different magic number
// 2. they have different script types
// 3. their background colors differ (this is not covered by 1.)
if( pFnt->DifferentFontCacheId( pNew, pFnt->GetActual() ) ||
pNew->GetActual() != pFnt->GetActual() ||
( ! pNew->GetBackColor() && pFnt->GetBackColor() ) ||
( pNew->GetBackColor() && ! pFnt->GetBackColor() ) ||
( pNew->GetBackColor() && pFnt->GetBackColor() &&
( *pNew->GetBackColor() != *pFnt->GetBackColor() ) )
|| !pNew->GetActualFont().SvxFontSubsetEquals(pFnt->GetActualFont()))
{
pNew->SetTransparent( true );
pNew->SetAlign( ALIGN_BASELINE );
pInf->SetFont( pNew );
}
else
pFnt = nullptr;
pNew->Invalidate();
pNew->ChgPhysFnt( pInf->GetVsh(), *pInf->GetOut() );
if( pItr && pItr->GetFnt() == pFnt )
{
pIter = pItr;
pIter->SetFnt( pNew );
}
}
SwFontSave::~SwFontSave()
{
if( pFnt )
{
// Reset SwFont
pFnt->Invalidate();
pInf->SetFont( pFnt );
if( pIter )
{
pIter->SetFnt( pFnt );
pIter->m_nPosition = COMPLETE_STRING;
}
}
}
bool SwTextFormatInfo::ChgHyph( const bool bNew )
{
const bool bOld = m_bAutoHyph;
if( m_bAutoHyph != bNew )
{
m_bAutoHyph = bNew;
InitHyph( bNew );
// Set language in the Hyphenator
if( m_pFnt )
m_pFnt->ChgPhysFnt( m_pVsh, *m_pOut );
}
return bOld;
}
bool SwTextFormatInfo::CheckCurrentPosBookmark()
{
if (m_nLastBookmarkPos != GetIdx())
{
m_nLastBookmarkPos = GetIdx();
return true;
}
else
{
return false;
}
}
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */