tdf#61444 Correct Writer text layout across formatting changes

Previously, Writer performed text layout for each span of text
separately. This caused incorrect kerning when the text style changed
mid-word, for example by highlighting or changing the color of a single
letter.

This change updates Writer so it will also consider neighboring text
while performing text layout.

Change-Id: I511096c009343f39cc1b9ba745909c5b8cbad86f
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/167016
Tested-by: Jenkins
Reviewed-by: Jonathan Clark <jonathan@libreoffice.org>
This commit is contained in:
Jonathan Clark 2024-04-29 04:30:43 -06:00
parent 5e88d86d8c
commit 30d376fb7d
22 changed files with 605 additions and 184 deletions

View file

@ -1059,6 +1059,16 @@ public:
vcl::text::TextLayoutCache const* = nullptr,
SalLayoutGlyphs const*const pLayoutCache = nullptr) const;
void DrawPartialTextArray(const Point& rStartPt, const OUString& rStr, KernArraySpan aKernArray,
std::span<const sal_Bool> pKashidaAry, sal_Int32 nIndex,
sal_Int32 nLen, sal_Int32 nPartIndex, sal_Int32 nPartLen,
SalLayoutFlags flags = SalLayoutFlags::NONE,
const SalLayoutGlyphs* pLayoutCache = nullptr);
double GetPartialTextArray(const OUString& rStr, KernArray* pDXAry, sal_Int32 nIndex,
sal_Int32 nLen, sal_Int32 nPartIndex, sal_Int32 nPartLen,
bool bCaret = false, const vcl::text::TextLayoutCache* = nullptr,
const SalLayoutGlyphs* pLayoutCache = nullptr) const;
SAL_DLLPRIVATE void GetCaretPositions( const OUString&, KernArray& rCaretXArray,
sal_Int32 nIndex, sal_Int32 nLen,
const SalLayoutGlyphs* pGlyphs = nullptr ) const;

View file

@ -97,6 +97,16 @@ public:
virtual sal_Int32 GetTextBreak(double nMaxWidth, double nCharExtra, int nFactor) const = 0;
virtual double FillDXArray( std::vector<double>* pDXArray, const OUString& rStr ) const = 0;
virtual double GetTextWidth() const { return FillDXArray( nullptr, {} ); }
virtual double FillPartialDXArray(std::vector<double>* pDXArray, const OUString& rStr,
sal_Int32 skipStart, sal_Int32 amt) const
= 0;
virtual double GetPartialTextWidth(sal_Int32 skipStart, sal_Int32 amt) const
{
return FillPartialDXArray(nullptr, {}, skipStart, amt);
}
virtual void GetCaretPositions( std::vector<double>& rCaretPositions, const OUString& rStr ) const = 0;
virtual bool IsKashidaPosValid ( int /*nCharPos*/, int /*nNextCharPos*/ ) const = 0; // i60594

View file

@ -2507,7 +2507,8 @@ CPPUNIT_TEST_FIXTURE(SwUiWriterTest7, testTdf87922)
const OUString& rText = aNodeIndex.GetNode().GetTextNode()->GetText();
sal_Int32 nLength = rText.getLength();
SwDrawTextInfo aDrawTextInfo(pWrtShell, *pWrtShell->GetOut(), pScriptInfo, rText,
TextFrameIndex(0), TextFrameIndex(nLength));
TextFrameIndex(0), TextFrameIndex(nLength),
/*layout context*/ std::nullopt);
// Root -> page -> body -> text.
SwTextFrame* pTextFrame
= static_cast<SwTextFrame*>(pWrtShell->GetLayout()->GetLower()->GetLower()->GetLower());

View file

@ -26,6 +26,7 @@
#include <swtypes.hxx>
#include "TextFrameIndex.hxx"
#include <swdllapi.h>
#include "swporlayoutcontext.hxx"
class SwTextFrame;
class SwViewShell;
@ -44,127 +45,94 @@ class SwUnderlineFont;
// encapsulates information for drawing text
class SW_DLLPUBLIC SwDrawTextInfo
{
const SwTextFrame* m_pFrame;
const SwTextFrame* m_pFrame = nullptr;
VclPtr<OutputDevice> m_pOut;
SwViewShell const * m_pSh;
SwViewShell const* m_pSh;
const SwScriptInfo* m_pScriptInfo;
Point m_aPos;
vcl::text::TextLayoutCache const* m_pCachedVclData;
OUString m_aText;
sw::WrongListIterator* m_pWrong;
sw::WrongListIterator* m_pGrammarCheck;
sw::WrongListIterator* m_pSmartTags;
sw::WrongListIterator* m_pWrong = nullptr;
sw::WrongListIterator* m_pGrammarCheck = nullptr;
sw::WrongListIterator* m_pSmartTags = nullptr;
Size m_aSize;
SwFont *m_pFnt;
SwUnderlineFont* m_pUnderFnt;
TextFrameIndex* m_pHyphPos;
tools::Long m_nKanaDiff;
SwFont* m_pFnt = nullptr;
SwUnderlineFont* m_pUnderFnt = nullptr;
TextFrameIndex* m_pHyphPos = nullptr;
tools::Long m_nKanaDiff = 0;
TextFrameIndex m_nIdx;
TextFrameIndex m_nLen;
TextFrameIndex m_nMeasureLen;
TextFrameIndex m_nMeasureLen = TextFrameIndex{ COMPLETE_STRING };
std::optional<SwLinePortionLayoutContext> m_nLayoutContext;
/// this is not a string index
sal_Int32 m_nOfst;
sal_Int32 m_nOfst = 0;
sal_uInt16 m_nWidth;
sal_uInt16 m_nAscent;
sal_uInt16 m_nCompress;
tools::Long m_nCharacterSpacing;
tools::Long m_nSpace;
tools::Long m_nKern;
TextFrameIndex m_nNumberOfBlanks;
sal_uInt8 m_nCursorBidiLevel;
sal_uInt16 m_nAscent = 0;
sal_uInt16 m_nCompress = 0;
tools::Long m_nCharacterSpacing = 0;
tools::Long m_nSpace = 0;
tools::Long m_nKern = 0;
TextFrameIndex m_nNumberOfBlanks = TextFrameIndex{ 0 };
sal_uInt8 m_nCursorBidiLevel = 0;
bool m_bBullet : 1;
bool m_bUpper : 1; // for small caps: upper case flag
bool m_bDrawSpace : 1; // for small caps: underline/ line through
bool m_bGreyWave : 1; // grey wave line for extended text input
bool m_bUpper : 1 = false; // for small caps: upper case flag
bool m_bDrawSpace : 1 = false; // for small caps: underline/ line through
bool m_bGreyWave : 1 = false; // grey wave line for extended text input
// For underlining we need to know, if a section is right in front of a
// whole block or a fix margin section.
bool m_bSpaceStop : 1;
bool m_bSnapToGrid : 1; // Does paragraph snap to grid?
bool m_bSpaceStop : 1 = false;
bool m_bSnapToGrid : 1 = false; // Does paragraph snap to grid?
// Paint text as if text has LTR direction, used for line numbering
bool m_bIgnoreFrameRTL : 1;
bool m_bIgnoreFrameRTL : 1 = false;
// GetModelPositionForViewPoint should not return the next position if screen position is
// inside second half of bound rect, used for Accessibility
bool m_bPosMatchesBounds :1;
bool m_bPosMatchesBounds : 1 = false;
#ifdef DBG_UTIL
// These flags should control that the appropriate Set-function has been
// called before calling the Get-function of a member
bool m_bPos : 1;
bool m_bWrong : 1;
bool m_bGrammarCheck : 1;
bool m_bSize : 1;
bool m_bFnt : 1;
bool m_bHyph : 1;
bool m_bKana : 1;
bool m_bOfst : 1;
bool m_bAscent: 1;
bool m_bCharacterSpacing : 1;
bool m_bSpace : 1;
bool m_bNumberOfBlanks : 1;
bool m_bUppr : 1;
bool m_bDrawSp: 1;
bool m_bPos : 1 = false;
bool m_bWrong : 1 = false;
bool m_bGrammarCheck : 1 = false;
bool m_bSize : 1 = false;
bool m_bFnt : 1 = false;
bool m_bHyph : 1 = false;
bool m_bKana : 1 = false;
bool m_bOfst : 1 = false;
bool m_bAscent : 1 = false;
bool m_bCharacterSpacing : 1 = false;
bool m_bSpace : 1 = false;
bool m_bNumberOfBlanks : 1 = false;
bool m_bUppr : 1 = false;
bool m_bDrawSp : 1 = false;
#endif
public:
/// constructor for simple strings
SwDrawTextInfo( SwViewShell const *pSh, OutputDevice &rOut,
const OUString &rText, sal_Int32 const nIdx, sal_Int32 const nLen,
sal_uInt16 nWidth = 0, bool bBullet = false)
: SwDrawTextInfo(pSh, rOut, nullptr, rText, TextFrameIndex(nIdx), TextFrameIndex(nLen), nWidth, bBullet)
SwDrawTextInfo(SwViewShell const* pSh, OutputDevice& rOut, const OUString& rText,
sal_Int32 const nIdx, sal_Int32 const nLen, sal_uInt16 nWidth = 0,
bool bBullet = false)
: SwDrawTextInfo(pSh, rOut, nullptr, rText, TextFrameIndex(nIdx), TextFrameIndex(nLen),
/*layout context*/ std::nullopt, nWidth, bBullet)
{}
/// constructor for text frame contents
SwDrawTextInfo( SwViewShell const *pSh, OutputDevice &rOut, const SwScriptInfo* pSI,
const OUString &rText, TextFrameIndex const nIdx, TextFrameIndex const nLen,
sal_uInt16 nWidth = 0, bool bBullet = false,
vcl::text::TextLayoutCache const*const pCachedVclData = nullptr)
: m_pCachedVclData(pCachedVclData)
SwDrawTextInfo(SwViewShell const* pSh, OutputDevice& rOut, const SwScriptInfo* pSI,
const OUString& rText, TextFrameIndex const nIdx, TextFrameIndex const nLen,
std::optional<SwLinePortionLayoutContext> nLayoutContext, sal_uInt16 nWidth = 0,
bool bBullet = false,
vcl::text::TextLayoutCache const* const pCachedVclData = nullptr)
: m_pOut(&rOut)
, m_pSh(pSh)
, m_pScriptInfo(pSI)
, m_pCachedVclData(pCachedVclData)
, m_aText(rText)
, m_nIdx(nIdx)
, m_nLen(nLen)
, m_nLayoutContext(nLayoutContext)
, m_nWidth(nWidth)
, m_bBullet(bBullet)
{
m_pFrame = nullptr;
m_pSh = pSh;
m_pOut = &rOut;
m_pScriptInfo = pSI;
m_aText = rText;
m_nIdx = nIdx;
m_nLen = nLen;
m_nMeasureLen = TextFrameIndex(COMPLETE_STRING);
m_nKern = 0;
m_nCompress = 0;
m_nWidth = nWidth;
m_nNumberOfBlanks = TextFrameIndex(0);
m_nCursorBidiLevel = 0;
m_bBullet = bBullet;
m_pUnderFnt = nullptr;
m_bGreyWave = false;
m_bSpaceStop = false;
m_bSnapToGrid = false;
m_bIgnoreFrameRTL = false;
m_bPosMatchesBounds = false;
// These values are initialized but have to be set explicitly via their
// Set-function before they may be accessed by their Get-function:
m_pWrong = nullptr;
m_pGrammarCheck = nullptr;
m_pSmartTags = nullptr;
m_pFnt = nullptr;
m_pHyphPos = nullptr;
m_nKanaDiff = 0;
m_nOfst = 0;
m_nAscent = 0;
m_nCharacterSpacing = 0;
m_nSpace = 0;
m_bUpper = false;
m_bDrawSpace = false;
#ifdef DBG_UTIL
// these flags control whether the matching member variables have been
// set by using the Set-function before they may be accessed by their
// Get-function:
m_bPos = m_bWrong = m_bGrammarCheck = m_bSize = m_bFnt = m_bAscent =
m_bSpace = m_bNumberOfBlanks = m_bUppr =
m_bDrawSp = m_bKana = m_bOfst = m_bHyph =
m_bCharacterSpacing = false;
#endif
}
const SwTextFrame* GetFrame() const
@ -280,6 +248,8 @@ public:
return m_nMeasureLen;
}
std::optional<SwLinePortionLayoutContext> GetLayoutContext() const { return m_nLayoutContext; }
sal_Int32 GetOffset() const
{
#ifdef DBG_UTIL
@ -445,6 +415,11 @@ public:
m_nLen = nNewLen;
}
void SetLayoutContext(std::optional<SwLinePortionLayoutContext> nNew)
{
m_nLayoutContext = nNew;
}
void SetWrong(sw::WrongListIterator *const pNew)
{
m_pWrong = pNew;

View file

@ -0,0 +1,27 @@
/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */
/*
* 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/.
*/
#pragma once
#include <sal/types.h>
class SwLinePortionLayoutContext
{
public:
sal_Int32 m_nBegin = -1;
sal_Int32 m_nEnd = -1;
SwLinePortionLayoutContext() = default;
SwLinePortionLayoutContext(sal_Int32 nBegin, sal_Int32 nEnd)
: m_nBegin(nBegin)
, m_nEnd(nEnd)
{
}
};
/* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */

View file

@ -80,7 +80,8 @@ bool maybeAdjustPositionsForBlockAdjust(TextFrameIndex& rCutPos, TextFrameIndex&
TextFrameIndex& rBreakStart, sal_uInt16& rBreakWidth,
sal_uInt16& rExtraBlankWidth, sal_uInt16& rMaxSizeDiff,
const SwTextFormatInfo& rInf, const SwScriptInfo& rSI,
sal_uInt16 maxComp)
sal_uInt16 maxComp,
std::optional<SwLinePortionLayoutContext> nLayoutContext)
{
const auto& adjObj = rInf.GetTextFrame()->GetTextNodeForParaProps()->GetSwAttrSet().GetAdjust();
const SvxAdjust& adjust = adjObj.GetAdjust();
@ -139,10 +140,10 @@ bool maybeAdjustPositionsForBlockAdjust(TextFrameIndex& rCutPos, TextFrameIndex&
rBreakStart = rCutPos = newCutPos;
rBreakPos = breakPos;
rInf.GetTextSize(&rSI, rInf.GetIdx(), breakPos - rInf.GetIdx(), maxComp, rBreakWidth,
rMaxSizeDiff, rInf.GetCachedVclData().get());
rInf.GetTextSize(&rSI, breakPos, rBreakStart - breakPos, maxComp, rExtraBlankWidth,
rMaxSizeDiff, rInf.GetCachedVclData().get());
rInf.GetTextSize(&rSI, rInf.GetIdx(), breakPos - rInf.GetIdx(), nLayoutContext, maxComp,
rBreakWidth, rMaxSizeDiff, rInf.GetCachedVclData().get());
rInf.GetTextSize(&rSI, breakPos, rBreakStart - breakPos, nLayoutContext, maxComp,
rExtraBlankWidth, rMaxSizeDiff, rInf.GetCachedVclData().get());
return false; // require SwHolePortion creation
}
@ -247,8 +248,8 @@ bool SwTextGuess::Guess( const SwTextPortion& rPor, SwTextFormatInfo &rInf,
( bUnbreakableNumberings && rPor.IsNumberPortion() ) )
{
// call GetTextSize with maximum compression (for kanas)
rInf.GetTextSize( &rSI, rInf.GetIdx(), nMaxLen,
nMaxComp, m_nBreakWidth, nMaxSizeDiff );
rInf.GetTextSize(&rSI, rInf.GetIdx(), nMaxLen, rPor.GetLayoutContext(), nMaxComp,
m_nBreakWidth, nMaxSizeDiff);
if ( ( m_nBreakWidth <= nLineWidth ) || ( bUnbreakableNumberings && rPor.IsNumberPortion() ) )
{
@ -257,7 +258,8 @@ bool SwTextGuess::Guess( const SwTextPortion& rPor, SwTextFormatInfo &rInf,
bool bRet = rPor.InFieldGrp()
|| maybeAdjustPositionsForBlockAdjust(m_nCutPos, m_nBreakPos, m_nBreakStart,
m_nBreakWidth, m_nExtraBlankWidth,
nMaxSizeDiff, rInf, rSI, nMaxComp);
nMaxSizeDiff, rInf, rSI, nMaxComp,
rPor.GetLayoutContext());
if( nItalic &&
(m_nCutPos >= TextFrameIndex(rInf.GetText().getLength()) ||
// #i48035# Needed for CalcFitToContent
@ -419,8 +421,8 @@ bool SwTextGuess::Guess( const SwTextPortion& rPor, SwTextFormatInfo &rInf,
if ( TextFrameIndex(COMPLETE_STRING) != m_nCutPos )
{
sal_uInt16 nMinSize;
rInf.GetTextSize( &rSI, rInf.GetIdx(), m_nCutPos - rInf.GetIdx(),
nMaxComp, nMinSize, nMaxSizeDiff );
rInf.GetTextSize(&rSI, rInf.GetIdx(), m_nCutPos - rInf.GetIdx(),
rPor.GetLayoutContext(), nMaxComp, nMinSize, nMaxSizeDiff);
OSL_ENSURE( nMinSize <= nLineWidth, "What a Guess!!!" );
}
#endif
@ -430,8 +432,8 @@ bool SwTextGuess::Guess( const SwTextPortion& rPor, SwTextFormatInfo &rInf,
{
// second check if everything fits to line
m_nCutPos = m_nBreakPos = rInf.GetIdx() + nMaxLen - TextFrameIndex(1);
rInf.GetTextSize( &rSI, rInf.GetIdx(), nMaxLen, nMaxComp,
m_nBreakWidth, nMaxSizeDiff );
rInf.GetTextSize(&rSI, rInf.GetIdx(), nMaxLen, rPor.GetLayoutContext(), nMaxComp,
m_nBreakWidth, nMaxSizeDiff);
// The following comparison should always give true, otherwise
// there likely has been a pixel rounding error in GetTextBreak
@ -440,7 +442,8 @@ bool SwTextGuess::Guess( const SwTextPortion& rPor, SwTextFormatInfo &rInf,
bool bRet = rPor.InFieldGrp()
|| maybeAdjustPositionsForBlockAdjust(m_nCutPos, m_nBreakPos, m_nBreakStart,
m_nBreakWidth, m_nExtraBlankWidth,
nMaxSizeDiff, rInf, rSI, nMaxComp);
nMaxSizeDiff, rInf, rSI, nMaxComp,
rPor.GetLayoutContext());
if (nItalic && (m_nBreakPos + TextFrameIndex(1)) >= TextFrameIndex(rInf.GetText().getLength()))
m_nBreakWidth += nItalic;
@ -731,9 +734,8 @@ bool SwTextGuess::Guess( const SwTextPortion& rPor, SwTextFormatInfo &rInf,
if( nPorLen )
{
rInf.GetTextSize( &rSI, rInf.GetIdx(), nPorLen,
nMaxComp, m_nBreakWidth, nMaxSizeDiff,
rInf.GetCachedVclData().get() );
rInf.GetTextSize(&rSI, rInf.GetIdx(), nPorLen, rPor.GetLayoutContext(), nMaxComp,
m_nBreakWidth, nMaxSizeDiff, rInf.GetCachedVclData().get());
// save maximum width for later use
if ( nMaxSizeDiff )
@ -747,8 +749,9 @@ bool SwTextGuess::Guess( const SwTextPortion& rPor, SwTextFormatInfo &rInf,
if (m_nBreakStart > rInf.GetIdx() + nPorLen + m_nFieldDiff)
{
rInf.GetTextSize(&rSI, rInf.GetIdx() + nPorLen,
m_nBreakStart - rInf.GetIdx() - nPorLen - m_nFieldDiff, nMaxComp,
m_nExtraBlankWidth, nMaxSizeDiff, rInf.GetCachedVclData().get());
m_nBreakStart - rInf.GetIdx() - nPorLen - m_nFieldDiff,
rPor.GetLayoutContext(), nMaxComp, m_nExtraBlankWidth, nMaxSizeDiff,
rInf.GetCachedVclData().get());
}
if( m_pHanging )

View file

@ -402,7 +402,8 @@ SwPosSize SwTextSizeInfo::GetTextSize( OutputDevice* pOutDev,
const TextFrameIndex nIndex,
const TextFrameIndex nLength) const
{
SwDrawTextInfo aDrawInf( m_pVsh, *pOutDev, pSI, rText, nIndex, nLength );
SwDrawTextInfo aDrawInf(m_pVsh, *pOutDev, pSI, rText, nIndex, nLength,
/*layout context*/ std::nullopt);
aDrawInf.SetFrame( m_pFrame );
aDrawInf.SetFont( m_pFnt );
aDrawInf.SetSnapToGrid( SnapToGrid() );
@ -410,7 +411,8 @@ SwPosSize SwTextSizeInfo::GetTextSize( OutputDevice* pOutDev,
return SwPosSize(m_pFnt->GetTextSize_( aDrawInf ));
}
SwPosSize SwTextSizeInfo::GetTextSize() const
SwPosSize
SwTextSizeInfo::GetTextSize(std::optional<SwLinePortionLayoutContext> nLayoutContext) const
{
const SwScriptInfo& rSI =
const_cast<SwParaPortion*>(GetParaPortion())->GetScriptInfo();
@ -423,7 +425,7 @@ SwPosSize SwTextSizeInfo::GetTextSize() const
GetKanaComp() :
0 ;
SwDrawTextInfo aDrawInf( m_pVsh, *m_pOut, &rSI, *m_pText, m_nIdx, m_nLen );
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 );
@ -432,13 +434,15 @@ SwPosSize SwTextSizeInfo::GetTextSize() const
return SwPosSize(m_pFnt->GetTextSize_( aDrawInf ));
}
void SwTextSizeInfo::GetTextSize( const SwScriptInfo* pSI, const TextFrameIndex nIndex,
const TextFrameIndex nLength, const sal_uInt16 nComp,
sal_uInt16& nMinSize, sal_uInt16& nMaxSizeDiff,
vcl::text::TextLayoutCache const*const pCache) const
void SwTextSizeInfo::GetTextSize(const SwScriptInfo* pSI, const TextFrameIndex nIndex,
const TextFrameIndex nLength,
std::optional<SwLinePortionLayoutContext> nLayoutContext,
const sal_uInt16 nComp, sal_uInt16& nMinSize,
sal_uInt16& nMaxSizeDiff,
vcl::text::TextLayoutCache const* const pCache) const
{
SwDrawTextInfo aDrawInf( m_pVsh, *m_pOut, pSI, *m_pText, nIndex, nLength,
0, false, pCache);
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() );
@ -457,8 +461,8 @@ TextFrameIndex SwTextSizeInfo::GetTextBreak( const tools::Long nLineWidth,
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, 0, false, pCache );
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() );
@ -478,8 +482,8 @@ TextFrameIndex SwTextSizeInfo::GetTextBreak( const tools::Long nLineWidth,
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, 0, false, pCache );
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() );
@ -632,8 +636,8 @@ void SwTextPaintInfo::DrawText_( const OUString &rText, const SwLinePortion &rPo
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.Width(), bBullet );
SwDrawTextInfo aDrawInf(m_pFrame->getRootFrame()->GetCurrShell(), *m_pOut, pSI, rText, nStart,
nLength, rPor.GetLayoutContext(), rPor.Width(), bBullet);
aDrawInf.SetUnderFnt( m_pUnderFnt );

View file

@ -155,6 +155,7 @@ protected:
TextFrameIndex m_nIdx;
TextFrameIndex m_nLen;
TextFrameIndex m_nMeasureLen;
std::optional<SwLinePortionLayoutContext> m_nLayoutContext;
sal_uInt16 m_nKanaIdx;
bool m_bOnWin : 1;
bool m_bNotEOL : 1;
@ -248,11 +249,12 @@ public:
SwPosSize GetTextSize( OutputDevice* pOut, const SwScriptInfo* pSI,
const OUString& rText, TextFrameIndex nIdx,
TextFrameIndex nLen ) const;
SwPosSize GetTextSize() const;
void GetTextSize( const SwScriptInfo* pSI, TextFrameIndex nIdx,
TextFrameIndex nLen, const sal_uInt16 nComp,
sal_uInt16& nMinSize, sal_uInt16& nMaxSizeDiff,
vcl::text::TextLayoutCache const* = nullptr) const;
SwPosSize GetTextSize(std::optional<SwLinePortionLayoutContext> nLayoutContext
= std::nullopt) const;
void GetTextSize(const SwScriptInfo* pSI, TextFrameIndex nIdx, TextFrameIndex nLen,
std::optional<SwLinePortionLayoutContext> nLayoutContext,
const sal_uInt16 nComp, sal_uInt16& nMinSize, sal_uInt16& nMaxSizeDiff,
vcl::text::TextLayoutCache const* = nullptr) const;
inline SwPosSize GetTextSize(const SwScriptInfo* pSI, TextFrameIndex nIdx,
TextFrameIndex nLen) const;
inline SwPosSize GetTextSize( const OUString &rText ) const;
@ -278,6 +280,13 @@ public:
void SetMeasureLen(const TextFrameIndex nNew) { m_nMeasureLen = nNew; }
void SetText( const OUString &rNew ){ m_pText = &rNew; }
std::optional<SwLinePortionLayoutContext> GetLayoutContext() const { return m_nLayoutContext; }
void SetLayoutContext(std::optional<SwLinePortionLayoutContext> nNew)
{
m_nLayoutContext = nNew;
}
// No Bullets for the symbol font!
bool IsNoSymbol() const
{ return RTL_TEXTENCODING_SYMBOL != m_pFnt->GetCharSet( m_pFnt->GetActual() ); }

View file

@ -1706,12 +1706,9 @@ TextFrameIndex SwTextCursor::GetModelPositionForViewPoint( SwPosition *pPos, con
SAL_WARN_IF( aSizeInf.GetIdx().get() + pPor->GetLen().get() > aSizeInf.GetText().getLength(), "sw", "portion and text are out of sync" );
TextFrameIndex nSafeLen( std::min(pPor->GetLen().get(), aSizeInf.GetText().getLength() - aSizeInf.GetIdx().get()) );
SwDrawTextInfo aDrawInf( aSizeInf.GetVsh(),
*aSizeInf.GetOut(),
&pPara->GetScriptInfo(),
aSizeInf.GetText(),
aSizeInf.GetIdx(),
nSafeLen );
SwDrawTextInfo aDrawInf(aSizeInf.GetVsh(), *aSizeInf.GetOut(),
&pPara->GetScriptInfo(), aSizeInf.GetText(),
aSizeInf.GetIdx(), nSafeLen, aSizeInf.GetLayoutContext());
// Drop portion works like a multi portion, just its parts are not portions
if( pPor->IsDropPortion() && static_cast<SwDropPortion*>(pPor)->GetLines() > 1 )

View file

@ -1354,6 +1354,7 @@ SwTextPortion *SwTextFormatter::NewTextPortion( SwTextFormatInfo &rInf )
// bookmarks
const TextFrameIndex nNextBookmark = m_pScriptInfo->NextBookmark(rInf.GetIdx());
auto nNextContext = std::min({ nNextChg, nNextScript, nNextDir });
nNextChg = std::min({ nNextChg, nNextAttr, nNextScript, nNextDir, nNextHidden, nNextBookmark });
// Turbo boost:
@ -1398,6 +1399,53 @@ SwTextPortion *SwTextFormatter::NewTextPortion( SwTextFormatInfo &rInf )
pPor->SetLen( nNextChg - rInf.GetIdx() );
rInf.SetLen( pPor->GetLen() );
// Generate a new layout context for the text portion. This is necessary
// for the first text portion in a paragraph, or for any successive
// portions that are outside of the bounds of the previous context.
if (!rInf.GetLayoutContext().has_value()
|| rInf.GetLayoutContext()->m_nEnd <= rInf.GetIdx().get())
{
// The layout context must terminate at special characters
sal_Int32 nEnd = rInf.GetIdx().get();
for (; nEnd < nNextContext.get(); ++nEnd)
{
bool bAtEnd = false;
switch (rInf.GetText()[nEnd])
{
case CH_TXTATR_BREAKWORD:
case CH_TXTATR_INWORD:
case CH_TXTATR_TAB:
case CH_TXT_ATR_INPUTFIELDSTART:
case CH_TXT_ATR_INPUTFIELDEND:
case CH_TXT_ATR_FORMELEMENT:
case CH_TXT_ATR_FIELDSTART:
case CH_TXT_ATR_FIELDSEP:
case CH_TXT_ATR_FIELDEND:
bAtEnd = true;
break;
default:
break;
}
if (bAtEnd)
{
break;
}
}
std::optional<SwLinePortionLayoutContext> nNewContext;
if (rInf.GetIdx().get() != nEnd)
{
nNewContext = SwLinePortionLayoutContext{ rInf.GetIdx().get(), nEnd };
}
rInf.SetLayoutContext(nNewContext);
}
pPor->SetLayoutContext(rInf.GetLayoutContext());
return pPor;
}

View file

@ -24,6 +24,7 @@
#include <txttypes.hxx>
#include <TextFrameIndex.hxx>
#include <rtl/ustring.hxx>
#include <swporlayoutcontext.hxx>
class SwTextSizeInfo;
class SwTextPaintInfo;
@ -48,6 +49,7 @@ private:
bool m_bJoinBorderWithPrev;
bool m_bJoinBorderWithNext;
SwTwips m_nExtraBlankWidth = 0; // width of spaces after the break
std::optional<SwLinePortionLayoutContext> m_nLayoutContext;
void Truncate_();
@ -72,6 +74,11 @@ public:
void ExtraBlankWidth(const SwTwips nNew) { m_nExtraBlankWidth = nNew; }
SwTwips GetHangingBaseline() const { return mnHangingBaseline; }
void SetHangingBaseline( const SwTwips nNewBaseline ) { mnHangingBaseline = nNewBaseline; }
std::optional<SwLinePortionLayoutContext> GetLayoutContext() const { return m_nLayoutContext; }
void SetLayoutContext(std::optional<SwLinePortionLayoutContext> nNew)
{
m_nLayoutContext = nNew;
}
// Insert methods
virtual SwLinePortion *Insert( SwLinePortion *pPortion );
@ -179,6 +186,7 @@ inline SwLinePortion &SwLinePortion::operator=(const SwLinePortion &rPortion)
m_bJoinBorderWithPrev = rPortion.m_bJoinBorderWithPrev;
m_bJoinBorderWithNext = rPortion.m_bJoinBorderWithNext;
m_nExtraBlankWidth = rPortion.m_nExtraBlankWidth;
m_nLayoutContext = rPortion.m_nLayoutContext;
return *this;
}
@ -191,7 +199,8 @@ inline SwLinePortion::SwLinePortion(const SwLinePortion &rPortion) :
mnWhichPor( rPortion.mnWhichPor ),
m_bJoinBorderWithPrev( rPortion.m_bJoinBorderWithPrev ),
m_bJoinBorderWithNext( rPortion.m_bJoinBorderWithNext ),
m_nExtraBlankWidth(rPortion.m_nExtraBlankWidth)
m_nExtraBlankWidth(rPortion.m_nExtraBlankWidth),
m_nLayoutContext(rPortion.m_nLayoutContext)
{
}

View file

@ -547,7 +547,7 @@ TextFrameIndex SwTextPortion::GetModelPositionForViewPoint(const sal_uInt16 nOfs
// The GetTextSize() assumes that the own length is correct
SwPosSize SwTextPortion::GetTextSize( const SwTextSizeInfo &rInf ) const
{
SwPosSize aSize = rInf.GetTextSize();
SwPosSize aSize = rInf.GetTextSize(GetLayoutContext());
if( !GetJoinBorderWithPrev() )
aSize.Width(aSize.Width() + rInf.GetFont()->GetLeftBorderSpace() );
if( !GetJoinBorderWithNext() )

View file

@ -743,27 +743,66 @@ static void lcl_DrawLineForWrongListData(
}
static void GetTextArray(const OutputDevice& rDevice, const OUString& rStr, KernArray& rDXAry,
sal_Int32 nIndex, sal_Int32 nLen, bool bCaret = false,
sal_Int32 nIndex, sal_Int32 nLen,
std::optional<SwLinePortionLayoutContext> nLayoutContext,
bool bCaret = false,
const vcl::text::TextLayoutCache* layoutCache = nullptr)
{
const SalLayoutGlyphs* pLayoutCache = SalLayoutGlyphsCache::self()->GetLayoutGlyphs(&rDevice, rStr, nIndex, nLen,
0, layoutCache);
rDevice.GetTextArray(rStr, &rDXAry, nIndex, nLen, bCaret, layoutCache, pLayoutCache);
if (nLayoutContext.has_value())
{
auto nStrEnd = nIndex + nLen;
auto nContextBegin = std::clamp(nLayoutContext->m_nBegin, sal_Int32{0}, nIndex);
auto nContextEnd = std::clamp(nLayoutContext->m_nEnd, nStrEnd, rStr.getLength());
auto nContextLen = nContextEnd - nContextBegin;
const SalLayoutGlyphs* pLayoutCache = SalLayoutGlyphsCache::self()->GetLayoutGlyphs(
&rDevice, rStr, nContextBegin, nContextLen, 0, layoutCache);
rDevice.GetPartialTextArray(rStr, &rDXAry, nContextBegin, nContextLen, nIndex, nLen, bCaret,
layoutCache, pLayoutCache);
}
else
{
const SalLayoutGlyphs* pLayoutCache = SalLayoutGlyphsCache::self()->GetLayoutGlyphs(
&rDevice, rStr, nIndex, nLen, 0, layoutCache);
rDevice.GetTextArray(rStr, &rDXAry, nIndex, nLen, bCaret, layoutCache, pLayoutCache);
}
}
static void GetTextArray(const OutputDevice& rOutputDevice, const SwDrawTextInfo& rInf, KernArray& rDXAry,
bool bCaret = false)
static void GetTextArray(const OutputDevice& rOutputDevice, const SwDrawTextInfo& rInf,
KernArray& rDXAry, bool bCaret = false)
{
return GetTextArray(rOutputDevice, rInf.GetText(), rDXAry, rInf.GetIdx().get(), rInf.GetLen().get(),
bCaret, rInf.GetVclCache());
GetTextArray(rOutputDevice, rInf.GetText(), rDXAry, rInf.GetIdx().get(), rInf.GetLen().get(),
rInf.GetLayoutContext(), bCaret, rInf.GetVclCache());
}
static void GetTextArray(const OutputDevice& rOutputDevice, const SwDrawTextInfo& rInf, KernArray& rDXAry,
sal_Int32 nLen, bool bCaret = false)
static void GetTextArray(const OutputDevice& rOutputDevice, const SwDrawTextInfo& rInf,
KernArray& rDXAry, sal_Int32 nLen, bool bCaret = false)
{
// Substring is fine.
assert( nLen <= rInf.GetLen().get());
return GetTextArray(rOutputDevice, rInf.GetText(), rDXAry, rInf.GetIdx().get(), nLen, bCaret, rInf.GetVclCache());
assert(nLen <= rInf.GetLen().get());
GetTextArray(rOutputDevice, rInf.GetText(), rDXAry, rInf.GetIdx().get(), nLen,
rInf.GetLayoutContext(), bCaret, rInf.GetVclCache());
}
static void DrawTextArray(OutputDevice& rOutputDevice, const Point& rStartPt, const OUString& rStr,
KernArray& aKernArray, std::span<const sal_Bool> pKashidaAry,
sal_Int32 nIndex, sal_Int32 nLen,
std::optional<SwLinePortionLayoutContext> nLayoutContext)
{
if (nLayoutContext.has_value())
{
auto nStrEnd = nIndex + nLen;
auto nContextBegin = std::clamp(nLayoutContext->m_nBegin, sal_Int32{0}, nIndex);
auto nContextEnd = std::clamp(nLayoutContext->m_nEnd, nStrEnd, rStr.getLength());
auto nContextLen = nContextEnd - nContextBegin;
rOutputDevice.DrawPartialTextArray(rStartPt, rStr, aKernArray, pKashidaAry, nContextBegin,
nContextLen, nIndex, nLen);
}
else
{
rOutputDevice.DrawTextArray(rStartPt, rStr, aKernArray, pKashidaAry, nIndex, nLen);
}
}
void SwFntObj::DrawText( SwDrawTextInfo &rInf )
@ -965,8 +1004,9 @@ void SwFntObj::DrawText( SwDrawTextInfo &rInf )
if ( bSwitchH2V )
rInf.GetFrame()->SwitchHorizontalToVertical( aTextOriginPos );
rInf.GetOut().DrawTextArray( aTextOriginPos, rInf.GetText(),
aKernArray, {}, sal_Int32(rInf.GetIdx()), sal_Int32(rInf.GetLen()));
DrawTextArray(rInf.GetOut(), aTextOriginPos, rInf.GetText(), aKernArray, {},
sal_Int32{ rInf.GetIdx() }, sal_Int32{ rInf.GetLen() },
rInf.GetLayoutContext());
return;
}
@ -1121,7 +1161,6 @@ void SwFntObj::DrawText( SwDrawTextInfo &rInf )
if (TextFrameIndex(1) == rInf.GetLen())
{
aKernArray.set(0, rInf.GetWidth() + nSpaceAdd);
rInf.GetOut().DrawTextArray( aTextOriginPos, rInf.GetText(),
aKernArray, aKashidaArray, sal_Int32(rInf.GetIdx()), 1 );
}
@ -1129,13 +1168,16 @@ void SwFntObj::DrawText( SwDrawTextInfo &rInf )
{
sal_Int32 nIndex(sal_Int32(rInf.GetLen()) - 2);
aKernArray.adjust(nIndex, nSpaceAdd);
rInf.GetOut().DrawTextArray( aTextOriginPos, rInf.GetText(),
aKernArray, aKashidaArray, sal_Int32(rInf.GetIdx()), sal_Int32(rInf.GetLen()));
DrawTextArray(rInf.GetOut(), aTextOriginPos, rInf.GetText(), aKernArray,
aKashidaArray, sal_Int32{ rInf.GetIdx() },
sal_Int32{ rInf.GetLen() }, rInf.GetLayoutContext());
}
}
else
rInf.GetOut().DrawTextArray( aTextOriginPos, rInf.GetText(),
aKernArray, aKashidaArray, sal_Int32(rInf.GetIdx()), sal_Int32(rInf.GetLen()));
else {
DrawTextArray(rInf.GetOut(), aTextOriginPos, rInf.GetText(), aKernArray,
aKashidaArray, sal_Int32{ rInf.GetIdx() },
sal_Int32{ rInf.GetLen() }, rInf.GetLayoutContext());
}
}
else
{
@ -1485,10 +1527,8 @@ void SwFntObj::DrawText( SwDrawTextInfo &rInf )
rInf.GetFrame()->SwitchHorizontalToVertical( aTextOriginPos );
sal_Int32 nIdx = sal_Int32(rInf.GetIdx());
const SalLayoutGlyphs* pGlyphs = SalLayoutGlyphsCache::self()->GetLayoutGlyphs(&rInf.GetOut(),
rInf.GetText(), nIdx, nLen);
rInf.GetOut().DrawTextArray( aTextOriginPos, rInf.GetText(), aKernArray, aKashidaArray,
nIdx, nLen, SalLayoutFlags::NONE, pGlyphs );
DrawTextArray(rInf.GetOut(), aTextOriginPos, rInf.GetText(), aKernArray,
aKashidaArray, nIdx, nLen, rInf.GetLayoutContext());
if (bBullet)
{
rInf.GetOut().Push();
@ -1633,8 +1673,8 @@ Size SwFntObj::GetTextSize( SwDrawTextInfo& rInf )
if( !GetScrFont()->IsSameInstance( rInf.GetOut().GetFont() ) )
rInf.GetOut().SetFont( *m_pScrFont );
GetTextArray(*m_pPrinter, rInf.GetText(), aKernArray,
sal_Int32(rInf.GetIdx()), sal_Int32(nLn), bCaret);
GetTextArray(*m_pPrinter, rInf.GetText(), aKernArray, sal_Int32(rInf.GetIdx()),
sal_Int32(nLn), rInf.GetLayoutContext(), bCaret);
}
else
{
@ -1989,8 +2029,8 @@ TextFrameIndex SwFont::GetTextBreak(SwDrawTextInfo const & rInf, tools::Long nTe
const sal_uInt16 nGridWidth = GetGridWidth(*pGrid, *pDoc);
KernArray aKernArray;
GetTextArray( rInf.GetOut(), rInf.GetText(), aKernArray,
sal_Int32(rInf.GetIdx()), sal_Int32(rInf.GetLen()));
GetTextArray(rInf.GetOut(), rInf.GetText(), aKernArray, sal_Int32(rInf.GetIdx()),
sal_Int32(rInf.GetLen()), rInf.GetLayoutContext());
if (pGrid->IsSnapToChars())
{
@ -2117,8 +2157,8 @@ TextFrameIndex SwFont::GetTextBreak(SwDrawTextInfo const & rInf, tools::Long nTe
else if (nLn > nTextBreak2 + nTextBreak2)
nLn = nTextBreak2 + nTextBreak2;
KernArray aKernArray;
GetTextArray( rInf.GetOut(), rInf.GetText(), aKernArray,
sal_Int32(rInf.GetIdx()), sal_Int32(nLn));
GetTextArray(rInf.GetOut(), rInf.GetText(), aKernArray, sal_Int32(rInf.GetIdx()),
sal_Int32(nLn), rInf.GetLayoutContext());
if( rInf.GetScriptInfo()->Compress( aKernArray, rInf.GetIdx(), nLn,
rInf.GetKanaComp(), o3tl::narrowing<sal_uInt16>(GetHeight( m_nActual )),
lcl_IsFullstopCentered( rInf.GetOut() ) ) )

View file

@ -225,7 +225,7 @@ TextFrameIndex SwFont::GetCapitalBreak( SwViewShell const * pSh, const OutputDev
// Start:
Point aPos( 0, 0 );
SwDrawTextInfo aInfo(pSh, *const_cast<OutputDevice*>(pOut), pScript, rText, nIdx, nLen,
0, false);
/*layout context*/ std::nullopt, 0, false);
aInfo.SetPos( aPos );
aInfo.SetSpace( 0 );
aInfo.SetWrong( nullptr );
@ -375,11 +375,9 @@ void SwDoCapitalCursorOfst::Do()
}
else
{
SwDrawTextInfo aDrawInf( m_rInf.GetShell(), *m_rInf.GetpOut(),
m_rInf.GetScriptInfo(),
m_rInf.GetText(),
m_rInf.GetIdx(),
m_rInf.GetLen(), 0, false );
SwDrawTextInfo aDrawInf(m_rInf.GetShell(), *m_rInf.GetpOut(), m_rInf.GetScriptInfo(),
m_rInf.GetText(), m_rInf.GetIdx(), m_rInf.GetLen(),
/*layout context*/ std::nullopt, 0, false);
aDrawInf.SetOffset(m_nOfst);
aDrawInf.SetKern( m_rInf.GetKern() );
aDrawInf.SetKanaComp( m_rInf.GetKanaComp() );

View file

@ -59,7 +59,10 @@ public:
SAL_DLLPRIVATE void DrawText(SalGraphics&) const override;
SAL_DLLPRIVATE sal_Int32 GetTextBreak(double nMaxWidth, double nCharExtra, int nFactor) const override;
SAL_DLLPRIVATE double GetTextWidth() const final override;
SAL_DLLPRIVATE double GetPartialTextWidth(sal_Int32 skipStart, sal_Int32 amt) const override;
SAL_DLLPRIVATE double FillDXArray(std::vector<double>* pDXArray, const OUString& rStr) const override;
SAL_DLLPRIVATE double FillPartialDXArray(std::vector<double>* pDXArray, const OUString& rStr,
sal_Int32 skipStart, sal_Int32 amt) const override;
SAL_DLLPRIVATE void GetCaretPositions(std::vector<double>& rCaretPositions, const OUString& rStr) const override;
SAL_DLLPRIVATE bool GetNextGlyph(const GlyphItem** pGlyph, basegfx::B2DPoint& rPos, int& nStart,
const LogicalFontInstance** ppGlyphFont = nullptr) const override;
@ -116,7 +119,10 @@ public:
// used by upper layers
double GetTextWidth() const final override;
double GetPartialTextWidth(sal_Int32 skipStart, sal_Int32 amt) const final override;
double FillDXArray(std::vector<double>* pDXArray, const OUString& rStr) const final override;
double FillPartialDXArray(std::vector<double>* pDXArray, const OUString& rStr,
sal_Int32 skipStart, sal_Int32 amt) const final override;
sal_Int32 GetTextBreak(double nMaxWidth, double nCharExtra, int nFactor) const final override;
void GetCaretPositions(std::vector<double>& rCaretPositions, const OUString& rStr) const override;

View file

@ -611,4 +611,70 @@ CPPUNIT_TEST_FIXTURE(VclComplexTextTest, testTdf107612)
#endif
}
CPPUNIT_TEST_FIXTURE(VclComplexTextTest, testPartialKoreanJamoComposition)
{
OUString aStr = u"은"_ustr;
vcl::Font aFont("DejaVu Sans", "Book", Size(0, 2048));
ScopedVclPtrInstance<VirtualDevice> pOutDev;
pOutDev->SetFont(aFont);
// Absolute character widths for the complete array.
KernArray aCompleteWidths;
auto nCompleteWidth = pOutDev->GetTextArray(aStr, &aCompleteWidths);
CPPUNIT_ASSERT_EQUAL(size_t{ 3 }, aCompleteWidths.size());
// Accumulate partial widths
double nPartialWidth = 0.0;
sal_Int32 nPrevWidth = 0;
for (sal_Int32 i = 0; i < 3; ++i)
{
KernArray aFragmentWidths;
auto nFragmentWidth = pOutDev->GetPartialTextArray(
aStr, &aFragmentWidths, /*nIndex*/ 0, /*nLen*/ 3, /*nPartIndex*/ i, /*nPartLen*/ 1);
nPartialWidth += nFragmentWidth;
CPPUNIT_ASSERT_EQUAL(size_t{ 1 }, aFragmentWidths.size());
CPPUNIT_ASSERT_EQUAL(aCompleteWidths[i] - nPrevWidth, aFragmentWidths[0]);
nPrevWidth = aCompleteWidths[i];
}
CPPUNIT_ASSERT_DOUBLES_EQUAL(nCompleteWidth, nPartialWidth, /*delta*/ 0.01);
}
CPPUNIT_TEST_FIXTURE(VclComplexTextTest, testPartialArabicComposition)
{
OUString aStr = u"سُكُونْ"_ustr;
vcl::Font aFont("DejaVu Sans", "Book", Size(0, 2048));
ScopedVclPtrInstance<VirtualDevice> pOutDev;
pOutDev->SetFont(aFont);
// Absolute character widths for the complete array.
KernArray aCompleteWidths;
auto nCompleteWidth = pOutDev->GetTextArray(aStr, &aCompleteWidths);
CPPUNIT_ASSERT_EQUAL(size_t{ 7 }, aCompleteWidths.size());
// Accumulate partial widths
double nPartialWidth = 0.0;
sal_Int32 nPrevWidth = 0;
for (sal_Int32 i = 0; i < 7; ++i)
{
KernArray aFragmentWidths;
auto nFragmentWidth = pOutDev->GetPartialTextArray(
aStr, &aFragmentWidths, /*nIndex*/ 0, /*nLen*/ 7, /*nPartIndex*/ i, /*nPartLen*/ 1);
nPartialWidth += nFragmentWidth;
CPPUNIT_ASSERT_EQUAL(size_t{ 1 }, aFragmentWidths.size());
CPPUNIT_ASSERT_EQUAL(aCompleteWidths[i] - nPrevWidth, aFragmentWidths[0]);
nPrevWidth = aCompleteWidths[i];
}
CPPUNIT_ASSERT_DOUBLES_EQUAL(nCompleteWidth, nPartialWidth, /*delta*/ 0.01);
}
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */

Binary file not shown.

View file

@ -4999,6 +4999,55 @@ CPPUNIT_TEST_FIXTURE(PdfExportTest2, testTdf159817)
CPPUNIT_ASSERT_EQUAL(basegfx::B2DPoint(138.6, 623.7), roundPoint(37));
}
// Tests that kerning is correctly applied across color changes
CPPUNIT_TEST_FIXTURE(PdfExportTest2, testTdf61444)
{
aMediaDescriptor["FilterName"] <<= OUString("writer_pdf_Export");
saveAsPDF(u"tdf61444.odt");
std::unique_ptr<vcl::pdf::PDFiumDocument> pPdfDocument = parsePDFExport();
CPPUNIT_ASSERT_EQUAL(1, pPdfDocument->getPageCount());
// Get the first page
std::unique_ptr<vcl::pdf::PDFiumPage> pPdfPage = pPdfDocument->openPage(/*nIndex*/ 0);
CPPUNIT_ASSERT(pPdfPage);
std::unique_ptr<vcl::pdf::PDFiumTextPage> pTextPage = pPdfPage->getTextPage();
CPPUNIT_ASSERT(pTextPage);
// 4 text objects should be present
int nPageObjectCount = pPdfPage->getObjectCount();
CPPUNIT_ASSERT_EQUAL(4, nPageObjectCount);
OUString sText[4];
basegfx::B2DRectangle aRect[4];
int nTextObjectCount = 0;
for (int i = 0; i < nPageObjectCount; ++i)
{
auto pPageObject = pPdfPage->getObject(i);
CPPUNIT_ASSERT_MESSAGE("no object", pPageObject != nullptr);
if (pPageObject->getType() == vcl::pdf::PDFPageObjectType::Text)
{
sText[nTextObjectCount] = pPageObject->getText(pTextPage);
aRect[nTextObjectCount] = pPageObject->getBounds();
++nTextObjectCount;
}
}
CPPUNIT_ASSERT_EQUAL(4, nTextObjectCount);
CPPUNIT_ASSERT_EQUAL(u"Wait"_ustr, sText[0].trim());
CPPUNIT_ASSERT_EQUAL(u"W"_ustr, sText[1].trim());
CPPUNIT_ASSERT_EQUAL(u"ai"_ustr, sText[2].trim());
CPPUNIT_ASSERT_EQUAL(u"t"_ustr, sText[3].trim());
// Both lines should have the same kerning, so should end at approximately the same X coordinate
auto solid_extent = aRect[0].getMaxX();
auto color_extent = aRect[3].getMaxX();
CPPUNIT_ASSERT_DOUBLES_EQUAL(solid_extent, color_extent, /*delta*/ 0.15);
}
} // end anonymous namespace
CPPUNIT_PLUGIN_IMPLEMENT();

View file

@ -846,6 +846,39 @@ CPPUNIT_TEST_FIXTURE(VclTextTest, testGetRightBottomAlignedMultiLineTextRect)
| DrawTextFlags::MultiLine));
}
CPPUNIT_TEST_FIXTURE(VclTextTest, testPartialTextArraySizeMatch)
{
OUString aWater = u"Water"_ustr;
vcl::Font aFont("DejaVu Sans", "Book", Size(0, 2048));
ScopedVclPtrInstance<VirtualDevice> pOutDev;
pOutDev->SetFont(aFont);
// Absolute character widths for the complete array.
KernArray aCompleteWidths;
auto nCompleteWidth = pOutDev->GetTextArray(aWater, &aCompleteWidths);
CPPUNIT_ASSERT_EQUAL(size_t{ 5 }, aCompleteWidths.size());
// Accumulate partial widths
double nPartialWidth = 0.0;
sal_Int32 nPrevWidth = 0;
for (sal_Int32 i = 0; i < 5; ++i)
{
KernArray aFragmentWidths;
auto nFragmentWidth = pOutDev->GetPartialTextArray(
aWater, &aFragmentWidths, /*nIndex*/ 0, /*nLen*/ 5, /*nPartIndex*/ i, /*nPartLen*/ 1);
nPartialWidth += nFragmentWidth;
CPPUNIT_ASSERT_EQUAL(size_t{ 1 }, aFragmentWidths.size());
CPPUNIT_ASSERT_EQUAL(aCompleteWidths[i] - nPrevWidth, aFragmentWidths[0]);
nPrevWidth = aCompleteWidths[i];
}
CPPUNIT_ASSERT_DOUBLES_EQUAL(nCompleteWidth, nPartialWidth, /*delta*/ 0.01);
}
CPPUNIT_PLUGIN_IMPLEMENT();
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */

View file

@ -274,6 +274,25 @@ double GenericSalLayout::FillDXArray( std::vector<double>* pCharWidths, const OU
return GetTextWidth();
}
double GenericSalLayout::FillPartialDXArray(std::vector<double>* pCharWidths, const OUString& rStr,
sal_Int32 skipStart, sal_Int32 amt) const
{
if (pCharWidths)
{
GetCharWidths(*pCharWidths, rStr);
// Strip excess characters from the array
if (skipStart < static_cast<sal_Int32>(pCharWidths->size()))
{
std::copy(pCharWidths->begin() + skipStart, pCharWidths->end(), pCharWidths->begin());
}
pCharWidths->resize(amt, 0.0);
}
return GetPartialTextWidth(skipStart, amt);
}
// the text width is the maximum logical extent of all glyphs
double GenericSalLayout::GetTextWidth() const
{
@ -287,6 +306,27 @@ double GenericSalLayout::GetTextWidth() const
return nWidth;
}
double GenericSalLayout::GetPartialTextWidth(sal_Int32 skipStart, sal_Int32 amt) const
{
if (!m_GlyphItems.IsValid())
{
return 0;
}
auto skipEnd = skipStart + amt;
double nWidth = 0.0;
for (auto const& aGlyphItem : m_GlyphItems)
{
auto pos = aGlyphItem.charPos();
if (pos >= skipStart && pos < skipEnd)
{
nWidth += aGlyphItem.newWidth();
}
}
return nWidth;
}
void GenericSalLayout::Justify(double nNewWidth)
{
double nOldWidth = GetTextWidth();
@ -1056,6 +1096,29 @@ double MultiSalLayout::GetTextWidth() const
return nWidth;
}
double MultiSalLayout::GetPartialTextWidth(sal_Int32 skipStart, sal_Int32 amt) const
{
// Measure text width. There might be holes in each SalLayout due to
// missing chars, so we use GetNextGlyph() to get the glyphs across all
// layouts.
int nStart = 0;
basegfx::B2DPoint aPos;
const GlyphItem* pGlyphItem;
auto skipEnd = skipStart + amt;
double nWidth = 0;
while (GetNextGlyph(&pGlyphItem, aPos, nStart))
{
auto cpos = pGlyphItem->charPos();
if (cpos >= skipStart && cpos < skipEnd)
{
nWidth += pGlyphItem->newWidth();
}
}
return nWidth;
}
double MultiSalLayout::FillDXArray( std::vector<double>* pCharWidths, const OUString& rStr ) const
{
if (pCharWidths)
@ -1089,6 +1152,25 @@ double MultiSalLayout::FillDXArray( std::vector<double>* pCharWidths, const OUSt
return GetTextWidth();
}
double MultiSalLayout::FillPartialDXArray(std::vector<double>* pCharWidths, const OUString& rStr,
sal_Int32 skipStart, sal_Int32 amt) const
{
if (pCharWidths)
{
FillDXArray(pCharWidths, rStr);
// Strip excess characters from the array
if (skipStart < static_cast<sal_Int32>(pCharWidths->size()))
{
std::copy(pCharWidths->begin() + skipStart, pCharWidths->end(), pCharWidths->begin());
}
pCharWidths->resize(amt);
}
return GetPartialTextWidth(skipStart, amt);
}
void MultiSalLayout::GetCaretPositions(std::vector<double>& rCaretPositions,
const OUString& rStr) const
{

View file

@ -689,6 +689,27 @@ float OutputDevice::approximate_digit_width() const
return GetTextWidth(u"0123456789"_ustr) / 10.0;
}
void OutputDevice::DrawPartialTextArray(const Point& rStartPt, const OUString& rStr,
KernArraySpan pDXArray,
std::span<const sal_Bool> pKashidaArray,
sal_Int32 /*nIndex*/, sal_Int32 /*nLen*/,
sal_Int32 nPartIndex, sal_Int32 nPartLen,
SalLayoutFlags flags, const SalLayoutGlyphs* pLayoutCache)
{
// Currently, this is just a wrapper for DrawTextArray().
//
// In certain documents, DrawTextArray/DrawPartialTextArray can be called such that combining
// characters straddle multiple draw calls. This can happen if, for example, a user attempts to
// use different text colors for a character and its diacritical marks.
//
// In order to fix this issue, this implementation should be replaced with one that performs
// correct glyph substitutions across text portion boundaries, using the extra layout context.
//
// See tdf#124116
DrawTextArray(rStartPt, rStr, pDXArray, pKashidaArray, nPartIndex, nPartLen, flags,
pLayoutCache);
}
void OutputDevice::DrawTextArray( const Point& rStartPt, const OUString& rStr,
KernArraySpan pDXAry,
std::span<const sal_Bool> pKashidaAry,
@ -728,6 +749,20 @@ double OutputDevice::GetTextArray( const OUString& rStr, KernArray* pKernArray,
sal_Int32 nIndex, sal_Int32 nLen, bool bCaret,
vcl::text::TextLayoutCache const*const pLayoutCache,
SalLayoutGlyphs const*const pSalLayoutCache) const
{
return GetPartialTextArray(rStr, pKernArray, nIndex, nLen, nIndex, nLen, bCaret, pLayoutCache,
pSalLayoutCache);
}
double OutputDevice::GetPartialTextArray(const OUString &rStr,
KernArray* pKernArray,
sal_Int32 nIndex,
sal_Int32 nLen,
sal_Int32 nPartIndex,
sal_Int32 nPartLen,
bool bCaret,
const vcl::text::TextLayoutCache* pLayoutCache,
const SalLayoutGlyphs* pSalLayoutCache) const
{
if( nIndex >= rStr.getLength() )
return 0; // TODO: this looks like a buggy caller?
@ -737,6 +772,11 @@ double OutputDevice::GetTextArray( const OUString& rStr, KernArray* pKernArray,
nLen = rStr.getLength() - nIndex;
}
if (nPartLen < 0 || nPartIndex + nPartLen >= rStr.getLength())
{
nPartLen = rStr.getLength() - nPartIndex;
}
std::vector<sal_Int32>* pDXAry = pKernArray ? &pKernArray->get_subunit_array() : nullptr;
// do layout
@ -752,7 +792,7 @@ double OutputDevice::GetTextArray( const OUString& rStr, KernArray* pKernArray,
// and hope that is sufficient.
if (pDXAry)
{
pDXAry->resize(nLen);
pDXAry->resize(nPartLen);
std::fill(pDXAry->begin(), pDXAry->end(), 0);
}
return 0;
@ -761,15 +801,29 @@ double OutputDevice::GetTextArray( const OUString& rStr, KernArray* pKernArray,
std::unique_ptr<std::vector<double>> xDXPixelArray;
if(pDXAry)
{
xDXPixelArray.reset(new std::vector<double>(nLen));
xDXPixelArray.reset(new std::vector<double>(nPartLen));
}
std::vector<double>* pDXPixelArray = xDXPixelArray.get();
double nWidth = pSalLayout->FillDXArray(pDXPixelArray, bCaret ? rStr : OUString());
double nWidth = 0.0;
// Fall back to the unbounded DX array when there is no expanded layout context. This is
// necessary for certain situations where characters are appended to the input string, such as
// automatic ellipsis.
if (nIndex == nPartIndex && nLen == nPartLen)
{
nWidth = pSalLayout->FillDXArray(pDXPixelArray, bCaret ? rStr : OUString());
}
else
{
nWidth = pSalLayout->FillPartialDXArray(pDXPixelArray, bCaret ? rStr : OUString(),
nPartIndex - nIndex, nPartLen);
}
// convert virtual char widths to virtual absolute positions
if( pDXPixelArray )
{
for( int i = 1; i < nLen; ++i )
for (int i = 1; i < nPartLen; ++i)
{
(*pDXPixelArray)[i] += (*pDXPixelArray)[i - 1];
}
@ -782,20 +836,20 @@ double OutputDevice::GetTextArray( const OUString& rStr, KernArray* pKernArray,
int nSubPixelFactor = pKernArray->get_factor();
if (mbMap)
{
for (int i = 0; i < nLen; ++i)
for (int i = 0; i < nPartLen; ++i)
(*pDXPixelArray)[i] = ImplDevicePixelToLogicWidthDouble((*pDXPixelArray)[i] * nSubPixelFactor);
}
else if (nSubPixelFactor)
{
for (int i = 0; i < nLen; ++i)
for (int i = 0; i < nPartLen; ++i)
(*pDXPixelArray)[i] *= nSubPixelFactor;
}
}
if (pDXAry)
{
pDXAry->resize(nLen);
for (int i = 0; i < nLen; ++i)
pDXAry->resize(nPartLen);
for (int i = 0; i < nPartLen; ++i)
(*pDXAry)[i] = basegfx::fround((*pDXPixelArray)[i]);
}