editeng/source/items/svxfont.cxx | 2 include/vcl/outdev.hxx | 2 include/vcl/vcllayout.hxx | 6 +- osx/soffice.xcodeproj/project.pbxproj | 7 ++ sw/source/core/inc/drawfont.hxx | 13 +++++ sw/source/core/text/inftxt.cxx | 12 ++++ sw/source/core/text/inftxt.hxx | 4 + sw/source/core/text/itrcrsr.cxx | 29 ++++++++++-- sw/source/core/txtnode/fntcache.cxx | 41 +++++++++++------ sw/source/core/txtnode/swfont.cxx | 16 ++++++ vcl/inc/sallayout.hxx | 7 +- vcl/qa/cppunit/complextext.cxx | 82 ++++++++++++++++++++++++++++++++++ vcl/source/gdi/CommonSalLayout.cxx | 50 ++++++++++++++++++-- vcl/source/gdi/sallayout.cxx | 18 ++++--- vcl/source/outdev/text.cxx | 8 +-- 15 files changed, 253 insertions(+), 44 deletions(-)
New commits: commit 8cb4db941f91cc234dd18c61f8b1e51f65360d1f Author: Khaled Hosny <kha...@aliftype.com> AuthorDate: Fri Aug 26 22:20:55 2022 +0200 Commit: Miklos Vajna <vmik...@collabora.com> CommitDate: Wed Aug 31 08:29:49 2022 +0200 tdf#30731: Improve caret travelling in Writer Previously, when measuring caret position, Writer would measure the width of the substring before the caret (i.e. layout it independent of the text after the caret and measure its width). This is incorrect, though. It assumes cutting the string laying it out would result in the same width as when laid out as part of a bigger string, which is invalid assumption when e.g. cutting inside a ligature or between letters that have different shapes when next to each other, etc. This appears to work when the width of the substring laid out alone is close enough to its width when laid out with the full text. But in cases where is widths are largely different, like the extreme case in the bug report, the caret will be jumping around as it is positioned based on the unligated glyphs not the ligated, rendered glyphs. This change introduces a special mode of measuring text width for caret positioning, that will layout the whole string that return the width of the requested substring. Fields and small caps text are trickier to handle, so old behaviour is retained for them. Now one will probably notice but if they do, it can be dealt with then. This also tries to be conservative and keep other pleases using the existing behaviour which might be desirable (e.g. when measuring text width for line breaking, we want the unligated width), but there might be other places that should use the new behaviour. To handle caret inside ligatures, the grapheme clusters in the ligature are counted and the width of the whole ligature is distributed on them evenly. A further improvement would be using HarfBuzz API to get ligature caret positions for fonts that provide them, which helps when the ligature components have different widths. Change-Id: I02062e2e2e1b1a35c8f84307c0a8f5d743059ab5 Reviewed-on: https://gerrit.libreoffice.org/c/core/+/138889 Tested-by: Jenkins Reviewed-by: Miklos Vajna <vmik...@collabora.com> diff --git a/editeng/source/items/svxfont.cxx b/editeng/source/items/svxfont.cxx index 868b830c63cc..65191cab8ed7 100644 --- a/editeng/source/items/svxfont.cxx +++ b/editeng/source/items/svxfont.cxx @@ -37,7 +37,7 @@ static tools::Long GetTextArray( const OutputDevice* pOut, const OUString& rStr, { const SalLayoutGlyphs* layoutGlyphs = SalLayoutGlyphsCache::self()->GetLayoutGlyphs(pOut, rStr, nIndex, nLen); - return pOut->GetTextArray( rStr, pDXAry, nIndex, nLen, nullptr, layoutGlyphs); + return pOut->GetTextArray( rStr, pDXAry, nIndex, nLen, false, nullptr, layoutGlyphs); } SvxFont::SvxFont() diff --git a/include/vcl/outdev.hxx b/include/vcl/outdev.hxx index 27e7650e8f72..1162ef3c116e 100644 --- a/include/vcl/outdev.hxx +++ b/include/vcl/outdev.hxx @@ -1043,7 +1043,7 @@ public: SalLayoutFlags flags = SalLayoutFlags::NONE, const SalLayoutGlyphs* pLayoutCache = nullptr); tools::Long GetTextArray( const OUString& rStr, std::vector<sal_Int32>* pDXAry, - sal_Int32 nIndex = 0, sal_Int32 nLen = -1, + sal_Int32 nIndex = 0, sal_Int32 nLen = -1, bool bCaret = false, vcl::text::TextLayoutCache const* = nullptr, SalLayoutGlyphs const*const pLayoutCache = nullptr) const; diff --git a/include/vcl/vcllayout.hxx b/include/vcl/vcllayout.hxx index e63d365b49c4..cb88760cd98e 100644 --- a/include/vcl/vcllayout.hxx +++ b/include/vcl/vcllayout.hxx @@ -20,6 +20,7 @@ #pragma once #include <basegfx/polygon/b2dpolypolygon.hxx> +#include <i18nlangtag/languagetag.hxx> #include <tools/gen.hxx> #include <tools/degree.hxx> @@ -94,8 +95,8 @@ public: // methods using string indexing virtual sal_Int32 GetTextBreak(DeviceCoordinate nMaxWidth, DeviceCoordinate nCharExtra, int nFactor) const = 0; - virtual DeviceCoordinate FillDXArray( std::vector<DeviceCoordinate>* pDXArray ) const = 0; - virtual DeviceCoordinate GetTextWidth() const { return FillDXArray( nullptr ); } + virtual DeviceCoordinate FillDXArray( std::vector<DeviceCoordinate>* pDXArray, const OUString& rStr ) const = 0; + virtual DeviceCoordinate GetTextWidth() const { return FillDXArray( nullptr, {} ); } virtual void GetCaretPositions( int nArraySize, sal_Int32* pCaretXArray ) const = 0; virtual bool IsKashidaPosValid ( int /*nCharPos*/, int /*nNextCharPos*/ ) const = 0; // i60594 @@ -119,6 +120,7 @@ private: protected: int mnMinCharPos; int mnEndCharPos; + LanguageTag maLanguageTag; int mnUnitsPerPixel; Degree10 mnOrientation; diff --git a/osx/soffice.xcodeproj/project.pbxproj b/osx/soffice.xcodeproj/project.pbxproj index 7c201c130e24..4a2038c7d373 100644 --- a/osx/soffice.xcodeproj/project.pbxproj +++ b/osx/soffice.xcodeproj/project.pbxproj @@ -7,6 +7,8 @@ objects = { /* Begin PBXFileReference section */ + 29FD821128BC548D00159078 /* text */ = {isa = PBXFileReference; lastKnownFileType = folder; name = text; path = ../sw/source/core/text; sourceTree = "<group>"; }; + 29FD821228BC54AB00159078 /* txtnode */ = {isa = PBXFileReference; lastKnownFileType = folder; name = txtnode; path = ../sw/source/core/txtnode; sourceTree = "<group>"; }; 456E58CF277CB9C700FA12D2 /* unoshap2.cxx */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = unoshap2.cxx; path = ../svx/source/unodraw/unoshap2.cxx; sourceTree = "<group>"; }; 456E58D1277CC33E00FA12D2 /* unopage.cxx */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = unopage.cxx; path = ../svx/source/unodraw/unopage.cxx; sourceTree = "<group>"; }; BE017B8725AF2ABE00244ED8 /* autostyl.cxx */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = autostyl.cxx; path = ../sc/source/ui/docshell/autostyl.cxx; sourceTree = "<group>"; }; @@ -123,7 +125,6 @@ BE017BF825AF568900244ED8 /* solveroptions.cxx */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = solveroptions.cxx; path = ../sc/source/ui/miscdlgs/solveroptions.cxx; sourceTree = "<group>"; }; BE017BF925AF568900244ED8 /* mtrindlg.cxx */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = mtrindlg.cxx; path = ../sc/source/ui/miscdlgs/mtrindlg.cxx; sourceTree = "<group>"; }; BE017BFA25AF568900244ED8 /* linkarea.cxx */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = linkarea.cxx; path = ../sc/source/ui/miscdlgs/linkarea.cxx; sourceTree = "<group>"; }; - BE017BFB25AF568900244ED8 /* shtabdlg.cxx */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = shtabdlg.cxx; path = ../sc/source/ui/miscdlgs/shtabdlg.cxx; sourceTree = "<group>"; }; BE017BFB25AF568900244ED8 /* gototabdlg.cxx */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = gototabdlg.cxx; path = ../sc/source/ui/miscdlgs/gototabdlg.cxx; sourceTree = "<group>"; }; BE017BFC25AF568900244ED8 /* inscodlg.cxx */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = inscodlg.cxx; path = ../sc/source/ui/miscdlgs/inscodlg.cxx; sourceTree = "<group>"; }; BE017BFD25AF568A00244ED8 /* crdlg.cxx */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = crdlg.cxx; path = ../sc/source/ui/miscdlgs/crdlg.cxx; sourceTree = "<group>"; }; @@ -1281,7 +1282,7 @@ BE017C1D25AF568B00244ED8 /* retypepassdlg.cxx */, BE017C0125AF568A00244ED8 /* scuiautofmt.cxx */, BE017C1C25AF568B00244ED8 /* sharedocdlg.cxx */, - BE017BFB25AF568900244ED8 /* shtabdlg.cxx */, + BE017BFB25AF568900244ED8 /* gototabdlg.cxx */, BE017C1F25AF568B00244ED8 /* simpref.cxx */, BE017BF825AF568900244ED8 /* solveroptions.cxx */, BE017C0C25AF568A00244ED8 /* solverutil.cxx */, @@ -2365,6 +2366,8 @@ BEBF3E662465907000415E87 /* sw */ = { isa = PBXGroup; children = ( + 29FD821228BC54AB00159078 /* txtnode */, + 29FD821128BC548D00159078 /* text */, BEBF3E6E246593A300415E87 /* ui */, BEBF3E672465907D00415E87 /* uibase */, ); diff --git a/sw/source/core/inc/drawfont.hxx b/sw/source/core/inc/drawfont.hxx index c40ff1044d70..9a02e881e10e 100644 --- a/sw/source/core/inc/drawfont.hxx +++ b/sw/source/core/inc/drawfont.hxx @@ -61,6 +61,7 @@ class SW_DLLPUBLIC SwDrawTextInfo tools::Long m_nKanaDiff; TextFrameIndex m_nIdx; TextFrameIndex m_nLen; + TextFrameIndex m_nMeasureLen; /// this is not a string index sal_Int32 m_nOfst; sal_uInt16 m_nWidth; @@ -127,6 +128,7 @@ public: m_aText = rText; m_nIdx = nIdx; m_nLen = nLen; + m_nMeasureLen = TextFrameIndex(COMPLETE_STRING); m_nKern = 0; m_nCompress = 0; m_nWidth = nWidth; @@ -274,6 +276,11 @@ public: return m_nLen; } + TextFrameIndex GetMeasureLen() const + { + return m_nMeasureLen; + } + sal_Int32 GetOffset() const { #ifdef DBG_UTIL @@ -488,6 +495,12 @@ public: m_nLen = nNew; } + void SetMeasureLen(TextFrameIndex const nNew) + { + assert( nNew == TextFrameIndex(COMPLETE_STRING) || nNew <= m_nLen ); + m_nMeasureLen = nNew; + } + void SetOffset( sal_Int32 nNew ) { m_nOfst = nNew; diff --git a/sw/source/core/text/inftxt.cxx b/sw/source/core/text/inftxt.cxx index 61a53c0aab76..1ca1046990a6 100644 --- a/sw/source/core/text/inftxt.cxx +++ b/sw/source/core/text/inftxt.cxx @@ -191,6 +191,7 @@ SwTextSizeInfo::SwTextSizeInfo() , m_pText(nullptr) , m_nIdx(0) , m_nLen(0) +, m_nMeasureLen(COMPLETE_STRING) , m_nKanaIdx(0) , m_bOnWin (false) , m_bNotEOL (false) @@ -221,6 +222,7 @@ SwTextSizeInfo::SwTextSizeInfo( const SwTextSizeInfo &rNew ) 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() ), @@ -309,7 +311,7 @@ void SwTextSizeInfo::CtorInitTextSizeInfo( OutputDevice* pRenderContext, SwTextF m_pText = &m_pFrame->GetText(); m_nIdx = nNewIdx; - m_nLen = TextFrameIndex(COMPLETE_STRING); + 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 = @@ -332,6 +334,7 @@ SwTextSizeInfo::SwTextSizeInfo( const SwTextSizeInfo &rNew, const OUString* pTex 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() ), @@ -407,6 +410,7 @@ SwPosSize SwTextSizeInfo::GetTextSize() const 0 ; SwDrawTextInfo aDrawInf( m_pVsh, *m_pOut, &rSI, *m_pText, m_nIdx, m_nLen ); + aDrawInf.SetMeasureLen( m_nMeasureLen ); aDrawInf.SetFrame( m_pFrame ); aDrawInf.SetFont( m_pFnt ); aDrawInf.SetSnapToGrid( SnapToGrid() ); @@ -1826,6 +1830,7 @@ SwTextSlot::SwTextSlot( , m_pOldGrammarCheckList(nullptr) , nIdx(0) , nLen(0) + , nMeasureLen(0) , pInf(nullptr) { if( rCh.isEmpty() ) @@ -1845,11 +1850,15 @@ SwTextSlot::SwTextSlot( 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 @@ -1923,6 +1932,7 @@ SwTextSlot::~SwTextSlot() pInf->SetText( *pOldText ); pInf->SetIdx( nIdx ); pInf->SetLen( nLen ); + pInf->SetMeasureLen( nMeasureLen ); // ST2 // Restore old smart tag list diff --git a/sw/source/core/text/inftxt.hxx b/sw/source/core/text/inftxt.hxx index 1621b4f35a8f..8011b29b442a 100644 --- a/sw/source/core/text/inftxt.hxx +++ b/sw/source/core/text/inftxt.hxx @@ -154,6 +154,7 @@ protected: const OUString *m_pText; TextFrameIndex m_nIdx; TextFrameIndex m_nLen; + TextFrameIndex m_nMeasureLen; sal_uInt16 m_nKanaIdx; bool m_bOnWin : 1; bool m_bNotEOL : 1; @@ -273,6 +274,8 @@ public: void SetIdx(const TextFrameIndex nNew) { m_nIdx = nNew; } TextFrameIndex GetLen() const { return m_nLen; } void SetLen(const TextFrameIndex nNew) { m_nLen = nNew; } + TextFrameIndex GetMeasureLen() const { return m_nMeasureLen; } + void SetMeasureLen(const TextFrameIndex nNew) { m_nMeasureLen = nNew; } void SetText( const OUString &rNew ){ m_pText = &rNew; } // No Bullets for the symbol font! @@ -682,6 +685,7 @@ class SwTextSlot final std::unique_ptr<sw::WrongListIterator> m_pTempIter; TextFrameIndex nIdx; TextFrameIndex nLen; + TextFrameIndex nMeasureLen; bool bOn; SwTextSizeInfo *pInf; diff --git a/sw/source/core/text/itrcrsr.cxx b/sw/source/core/text/itrcrsr.cxx index f8220650c827..47befa14d008 100644 --- a/sw/source/core/text/itrcrsr.cxx +++ b/sw/source/core/text/itrcrsr.cxx @@ -907,9 +907,21 @@ void SwTextCursor::GetCharRect_( SwRect* pOrig, TextFrameIndex const nOfst, } if ( pPor->PrtWidth() ) { + // tdf#30731: To get the correct nOfst width, we need + // to send the whole portion string to GetTextSize() + // and ask it to return the width of nOfst by calling + // SetMeasureLen(). Cutting the string at nOfst can + // give the wrong width if nOfst is in e.g. the middle + // of a ligature. See SwFntObj::DrawText(). TextFrameIndex const nOldLen = pPor->GetLen(); - pPor->SetLen( nOfst - aInf.GetIdx() ); aInf.SetLen( pPor->GetLen() ); + pPor->SetLen( nOfst - aInf.GetIdx() ); + aInf.SetMeasureLen(pPor->GetLen()); + if (aInf.GetLen() < aInf.GetMeasureLen()) + { + pPor->SetLen(aInf.GetMeasureLen()); + aInf.SetLen(pPor->GetLen()); + } if( nX || !pPor->InNumberGrp() ) { SeekAndChg( aInf ); @@ -925,7 +937,12 @@ void SwTextCursor::GetCharRect_( SwRect* pOrig, TextFrameIndex const nOfst, if( bWidth ) { pPor->SetLen(pPor->GetLen() + TextFrameIndex(1)); - aInf.SetLen( pPor->GetLen() ); + aInf.SetMeasureLen(pPor->GetLen()); + if (aInf.GetLen() < aInf.GetMeasureLen()) + { + pPor->SetLen(aInf.GetMeasureLen()); + aInf.SetLen(pPor->GetLen()); + } aInf.SetOnWin( false ); // no BULLETs! nTmp += pPor->GetTextSize( aInf ).Width(); aInf.SetOnWin( bOldOnWin ); @@ -1102,8 +1119,14 @@ void SwTextCursor::GetCharRect_( SwRect* pOrig, TextFrameIndex const nOfst, { const bool bOldOnWin = aInf.OnWin(); TextFrameIndex const nOldLen = pPor->GetLen(); - pPor->SetLen( TextFrameIndex(1) ); aInf.SetLen( pPor->GetLen() ); + pPor->SetLen( TextFrameIndex(1) ); + aInf.SetMeasureLen(pPor->GetLen()); + if (aInf.GetLen() < aInf.GetMeasureLen()) + { + pPor->SetLen(aInf.GetMeasureLen()); + aInf.SetLen(pPor->GetLen()); + } SeekAndChg( aInf ); aInf.SetOnWin( false ); // no BULLETs! aInf.SetKanaComp( pKanaComp ); diff --git a/sw/source/core/txtnode/fntcache.cxx b/sw/source/core/txtnode/fntcache.cxx index 146122841e7c..5e1a04fe7160 100644 --- a/sw/source/core/txtnode/fntcache.cxx +++ b/sw/source/core/txtnode/fntcache.cxx @@ -732,23 +732,27 @@ static void lcl_DrawLineForWrongListData( } static void GetTextArray(const OutputDevice& rDevice, const OUString& rStr, std::vector<sal_Int32>& rDXAry, - sal_Int32 nIndex, sal_Int32 nLen, const vcl::text::TextLayoutCache* layoutCache = nullptr) + sal_Int32 nIndex, sal_Int32 nLen, 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, layoutCache, pLayoutCache); + rDevice.GetTextArray(rStr, &rDXAry, nIndex, nLen, bCaret, layoutCache, pLayoutCache); } -static void GetTextArray(const OutputDevice& rOutputDevice, const SwDrawTextInfo& rInf, std::vector<sal_Int32>& rDXAry) +static void GetTextArray(const OutputDevice& rOutputDevice, const SwDrawTextInfo& rInf, std::vector<sal_Int32>& rDXAry, + bool bCaret = false) { - return GetTextArray(rOutputDevice, rInf.GetText(), rDXAry, rInf.GetIdx().get(), rInf.GetLen().get(), rInf.GetVclCache()); + return GetTextArray(rOutputDevice, rInf.GetText(), rDXAry, rInf.GetIdx().get(), rInf.GetLen().get(), + bCaret, rInf.GetVclCache()); } -static void GetTextArray(const OutputDevice& rOutputDevice, const SwDrawTextInfo& rInf, std::vector<sal_Int32>& rDXAry, sal_Int32 nLen) +static void GetTextArray(const OutputDevice& rOutputDevice, const SwDrawTextInfo& rInf, std::vector<sal_Int32>& 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, rInf.GetVclCache()); + return GetTextArray(rOutputDevice, rInf.GetText(), rDXAry, rInf.GetIdx().get(), nLen, bCaret, rInf.GetVclCache()); } void SwFntObj::DrawText( SwDrawTextInfo &rInf ) @@ -1529,6 +1533,15 @@ Size SwFntObj::GetTextSize( SwDrawTextInfo& rInf ) ? rInf.GetLen() : TextFrameIndex(rInf.GetText().getLength()); + const TextFrameIndex nMsrLn = (TextFrameIndex(COMPLETE_STRING) != rInf.GetMeasureLen()) + ? rInf.GetMeasureLen() + : nLn; + + // If the measure length is different from the length, then we are + // measuring substring width for caret positioning, see SetMeasureLength() + // use in TextCursor::GetCharRect_(). + bool bCaret(nMsrLn != nLn); + // be sure to have the correct layout mode at the printer if ( m_pPrinter ) { @@ -1566,7 +1579,7 @@ Size SwFntObj::GetTextSize( SwDrawTextInfo& rInf ) GetFontLeading( rInf.GetShell(), rInf.GetOut() ) ); std::vector<sal_Int32> aKernArray; - GetTextArray(*pOutDev, rInf, aKernArray, sal_Int32(rInf.GetLen())); + GetTextArray(*pOutDev, rInf, aKernArray, sal_Int32(nLn), bCaret); if (pGrid->IsSnapToChars()) { sw::Justify::SnapToGrid(aKernArray, rInf.GetText(), sal_Int32(rInf.GetIdx()), @@ -1579,7 +1592,7 @@ Size SwFntObj::GetTextSize( SwDrawTextInfo& rInf ) rInf.GetKern()); } - aTextSize.setWidth(aKernArray[sal_Int32(rInf.GetLen()) - 1]); + aTextSize.setWidth(aKernArray[sal_Int32(nMsrLn) - 1]); rInf.SetKanaDiff( 0 ); return aTextSize; } @@ -1609,7 +1622,7 @@ Size SwFntObj::GetTextSize( SwDrawTextInfo& rInf ) rInf.GetOut().SetFont( *m_pScrFont ); GetTextArray(*m_pPrinter, rInf.GetText(), aKernArray, - sal_Int32(rInf.GetIdx()), sal_Int32(nLn)); + sal_Int32(rInf.GetIdx()), sal_Int32(nLn), bCaret); } else { @@ -1617,7 +1630,7 @@ Size SwFntObj::GetTextSize( SwDrawTextInfo& rInf ) rInf.GetOut().SetFont( *m_pPrtFont ); aTextSize.setHeight( rInf.GetOut().GetTextHeight() ); - GetTextArray(rInf.GetOut(), rInf, aKernArray, nLn.get()); + GetTextArray(rInf.GetOut(), rInf, aKernArray, nLn.get(), bCaret); } if (bCompress) @@ -1628,16 +1641,16 @@ Size SwFntObj::GetTextSize( SwDrawTextInfo& rInf ) else rInf.SetKanaDiff( 0 ); - if (nLn) + if (nMsrLn) { - aTextSize.setWidth(aKernArray[sal_Int32(nLn) - 1]); + aTextSize.setWidth(aKernArray[sal_Int32(nMsrLn) - 1]); - // Note that we can't simply use sal_Int(nLn) - 1 as nSpaceCount + // Note that we can't simply use sal_Int(nMsrLn) - 1 as nSpaceCount // because a glyph may be made up of more than one characters. sal_Int32 nSpaceCount = 0; tools::Long nOldValue = aKernArray[0]; - for(sal_Int32 i = 1; i < sal_Int32(nLn); ++i) + for(sal_Int32 i = 1; i < sal_Int32(nMsrLn); ++i) { if (nOldValue != aKernArray[i]) { diff --git a/sw/source/core/txtnode/swfont.cxx b/sw/source/core/txtnode/swfont.cxx index 0ed7baf87894..c0149d9a6573 100644 --- a/sw/source/core/txtnode/swfont.cxx +++ b/sw/source/core/txtnode/swfont.cxx @@ -986,8 +986,16 @@ Size SwSubFont::GetTextSize_( SwDrawTextInfo& rInf ) ? TextFrameIndex(rInf.GetText().getLength()) : rInf.GetLen(); rInf.SetLen( nLn ); + if( IsCapital() && nLn ) + { + if (rInf.GetMeasureLen() != TextFrameIndex(COMPLETE_STRING)) + { + rInf.SetLen(rInf.GetMeasureLen()); + rInf.SetMeasureLen(TextFrameIndex(COMPLETE_STRING)); + } aTextSize = GetCapitalSize( rInf ); + } else { SV_STAT( nGetTextSize ); @@ -1009,17 +1017,25 @@ Size SwSubFont::GetTextSize_( SwDrawTextInfo& rInf ) // a single snippet since its size may differ, too. TextFrameIndex const nOldIdx(rInf.GetIdx()); TextFrameIndex const nOldLen(rInf.GetLen()); + TextFrameIndex const nOldMeasureLen(rInf.GetMeasureLen()); const OUString aSnippet(oldStr.copy(sal_Int32(nOldIdx), sal_Int32(nOldLen))); const OUString aNewText(CalcCaseMap(aSnippet)); rInf.SetText( aNewText ); rInf.SetIdx( TextFrameIndex(0) ); rInf.SetLen( TextFrameIndex(aNewText.getLength()) ); + if (nOldMeasureLen != TextFrameIndex(COMPLETE_STRING)) + { + const OUString aMeasureSnippet(oldStr.copy(sal_Int32(nOldIdx), sal_Int32(nOldMeasureLen))); + const OUString aNewMeasureText(CalcCaseMap(aMeasureSnippet)); + rInf.SetMeasureLen(TextFrameIndex(aNewMeasureText.getLength())); + } aTextSize = pLastFont->GetTextSize( rInf ); rInf.SetIdx( nOldIdx ); rInf.SetLen( nOldLen ); + rInf.SetMeasureLen(nOldMeasureLen); } else { diff --git a/vcl/inc/sallayout.hxx b/vcl/inc/sallayout.hxx index 165a6170bbc2..0210161b83d2 100644 --- a/vcl/inc/sallayout.hxx +++ b/vcl/inc/sallayout.hxx @@ -62,7 +62,7 @@ class MultiSalLayout final : public SalLayout public: void DrawText(SalGraphics&) const override; sal_Int32 GetTextBreak(DeviceCoordinate nMaxWidth, DeviceCoordinate nCharExtra, int nFactor) const override; - DeviceCoordinate FillDXArray(std::vector<DeviceCoordinate>* pDXArray) const override; + DeviceCoordinate FillDXArray(std::vector<DeviceCoordinate>* pDXArray, const OUString& rStr) const override; void GetCaretPositions(int nArraySize, sal_Int32* pCaretXArray) const override; bool GetNextGlyph(const GlyphItem** pGlyph, DevicePoint& rPos, int& nStart, const LogicalFontInstance** ppGlyphFont = nullptr, @@ -118,7 +118,7 @@ public: // used by upper layers DeviceCoordinate GetTextWidth() const final override; - DeviceCoordinate FillDXArray(std::vector<DeviceCoordinate>* pDXArray) const final override; + DeviceCoordinate FillDXArray(std::vector<DeviceCoordinate>* pDXArray, const OUString& rStr) const final override; sal_Int32 GetTextBreak(DeviceCoordinate nMaxWidth, DeviceCoordinate nCharExtra, int nFactor) const final override; void GetCaretPositions(int nArraySize, sal_Int32* pCaretXArray) const final override; @@ -145,7 +145,8 @@ private: void Justify(DeviceCoordinate nNewWidth); void ApplyAsianKerning(const OUString& rStr); - void GetCharWidths(std::vector<DeviceCoordinate>& rCharWidths) const; + void GetCharWidths(std::vector<DeviceCoordinate>& rCharWidths, + const OUString& rStr) const; void SetNeedFallback(vcl::text::ImplLayoutArgs&, sal_Int32, bool); diff --git a/vcl/qa/cppunit/complextext.cxx b/vcl/qa/cppunit/complextext.cxx index 6c35fd740b33..0b76f5eb91d9 100644 --- a/vcl/qa/cppunit/complextext.cxx +++ b/vcl/qa/cppunit/complextext.cxx @@ -52,12 +52,14 @@ public: void testTdf95650(); // Windows-only issue void testCaching(); void testCachingSubstring(); + void testCaret(); CPPUNIT_TEST_SUITE(VclComplexTextTest); CPPUNIT_TEST(testArabic); CPPUNIT_TEST(testTdf95650); CPPUNIT_TEST(testCaching); CPPUNIT_TEST(testCachingSubstring); + CPPUNIT_TEST(testCaret); CPPUNIT_TEST_SUITE_END(); }; @@ -231,6 +233,86 @@ void VclComplexTextTest::testCachingSubstring() testCachedGlyphsSubstring( text, "Dejavu Sans", false ); } +void VclComplexTextTest::testCaret() +{ +#if HAVE_MORE_FONTS + ScopedVclPtrInstance<WorkWindow> pWin(static_cast<vcl::Window *>(nullptr)); + CPPUNIT_ASSERT( pWin ); + + vcl::Font aFont("DejaVu Sans", "Book", Size(0, 200)); + + OutputDevice *pOutDev = pWin->GetOutDev(); + pOutDev->SetFont( aFont ); + + OUString aText; + std::vector<sal_Int32> aCharWidths, aRefCharWidths; + tools::Long nTextWidth, nTextWidth2; + + // A. RTL text + aText = u"لا بلا"; + + // 1) Regular DX array, the ligature width is given to the first components + // and the next ones are all zero width. + aRefCharWidths = { 114, 114, 178, 234, 353, 353 }; + aCharWidths.resize(aText.getLength()); + std::fill(aCharWidths.begin(), aCharWidths.end(), 0); + nTextWidth = pOutDev->GetTextArray(aText, &aCharWidths, 0, -1, /*bCaret*/false); + CPPUNIT_ASSERT_EQUAL(aRefCharWidths, aCharWidths); + CPPUNIT_ASSERT_EQUAL(tools::Long(353), nTextWidth); + CPPUNIT_ASSERT_EQUAL(sal_Int32(nTextWidth), aCharWidths.back()); + + // 2) Caret placement DX array, ligature width is distributed over its + // components. + aRefCharWidths = { 57, 114, 178, 234, 293, 353 }; + aCharWidths.resize(aText.getLength()); + std::fill(aCharWidths.begin(), aCharWidths.end(), 0); + nTextWidth = pOutDev->GetTextArray(aText, &aCharWidths, 0, -1, /*bCaret*/true); + CPPUNIT_ASSERT_EQUAL(aRefCharWidths, aCharWidths); + CPPUNIT_ASSERT_EQUAL(tools::Long(353), nTextWidth); + CPPUNIT_ASSERT_EQUAL(sal_Int32(nTextWidth), aCharWidths.back()); + + // 3) caret placement with combining marks, they should not add to ligature + // component count. + aText = u"لَاَ بلَاَ"; + aRefCharWidths = { 57, 57, 114, 114, 178, 234, 293, 293, 353, 353 }; + aCharWidths.resize(aText.getLength()); + std::fill(aCharWidths.begin(), aCharWidths.end(), 0); + nTextWidth2 = pOutDev->GetTextArray(aText, &aCharWidths, 0, -1, /*bCaret*/true); + CPPUNIT_ASSERT_EQUAL(aCharWidths[0], aCharWidths[1]); + CPPUNIT_ASSERT_EQUAL(aCharWidths[2], aCharWidths[3]); + CPPUNIT_ASSERT_EQUAL(aCharWidths[6], aCharWidths[7]); + CPPUNIT_ASSERT_EQUAL(aCharWidths[8], aCharWidths[9]); + CPPUNIT_ASSERT_EQUAL(aRefCharWidths, aCharWidths); + // FIXME: this should be 353, and the next assert should be true as well. + CPPUNIT_ASSERT_EQUAL(tools::Long(388), nTextWidth2); + //CPPUNIT_ASSERT_EQUAL(nTextWidth, nTextWidth2); + CPPUNIT_ASSERT_EQUAL(sal_Int32(nTextWidth), aCharWidths.back()); + + // B. LTR text + aText = u"fi fl ffi ffl"; + + // 1) Regular DX array, the ligature width is given to the first components + // and the next ones are all zero width. + aRefCharWidths = { 126, 126, 190, 316, 316, 380, 573, 573, 573, 637, 830, 830, 830 }; + aCharWidths.resize(aText.getLength()); + std::fill(aCharWidths.begin(), aCharWidths.end(), 0); + nTextWidth = pOutDev->GetTextArray(aText, &aCharWidths, 0, -1, /*bCaret*/false); + CPPUNIT_ASSERT_EQUAL(aRefCharWidths, aCharWidths); + CPPUNIT_ASSERT_EQUAL(tools::Long(830), nTextWidth); + CPPUNIT_ASSERT_EQUAL(sal_Int32(nTextWidth), aCharWidths.back()); + + // 2) Caret placement DX array, ligature width is distributed over its + // components. + aRefCharWidths = { 63, 126, 190, 253, 316, 380, 444, 508, 573, 637, 701, 765, 830 }; + aCharWidths.resize(aText.getLength()); + std::fill(aCharWidths.begin(), aCharWidths.end(), 0); + nTextWidth = pOutDev->GetTextArray(aText, &aCharWidths, 0, -1, /*bCaret*/true); + CPPUNIT_ASSERT_EQUAL(aRefCharWidths, aCharWidths); + CPPUNIT_ASSERT_EQUAL(tools::Long(830), nTextWidth); + CPPUNIT_ASSERT_EQUAL(sal_Int32(nTextWidth), aCharWidths.back()); +#endif +} + CPPUNIT_TEST_SUITE_REGISTRATION(VclComplexTextTest); /* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/source/gdi/CommonSalLayout.cxx b/vcl/source/gdi/CommonSalLayout.cxx index fc28200ff98a..f75dc12dedd0 100644 --- a/vcl/source/gdi/CommonSalLayout.cxx +++ b/vcl/source/gdi/CommonSalLayout.cxx @@ -26,6 +26,7 @@ #include <vcl/unohelp.hxx> #include <vcl/font/Feature.hxx> #include <vcl/font/FeatureParser.hxx> +#include <vcl/svapp.hxx> #include <ImplLayoutArgs.hxx> #include <TextLayoutCache.hxx> @@ -638,19 +639,58 @@ bool GenericSalLayout::LayoutText(vcl::text::ImplLayoutArgs& rArgs, const SalLay return true; } -void GenericSalLayout::GetCharWidths(std::vector<DeviceCoordinate>& rCharWidths) const +void GenericSalLayout::GetCharWidths(std::vector<DeviceCoordinate>& rCharWidths, const OUString& rStr) const { const int nCharCount = mnEndCharPos - mnMinCharPos; rCharWidths.clear(); rCharWidths.resize(nCharCount, 0); + css::uno::Reference<css::i18n::XBreakIterator> xBreak; + auto aLocale(maLanguageTag.getLocale()); + for (auto const& aGlyphItem : m_GlyphItems) { - const int nIndex = aGlyphItem.charPos() - mnMinCharPos; - if (nIndex >= nCharCount) + if (aGlyphItem.charPos() >= mnEndCharPos) continue; - rCharWidths[nIndex] += aGlyphItem.newWidth(); + if (aGlyphItem.charCount() > 1 && aGlyphItem.newWidth() != 0 && !rStr.isEmpty()) + { + // We are calculating DX array for cursor positions and this is a + // ligature, we want to distribute the glyph width over the + // ligature components. + if (!xBreak.is()) + xBreak = mxBreak.is() ? mxBreak : vcl::unohelper::CreateBreakIterator(); + + sal_Int32 nDone; + sal_Int32 nPos = aGlyphItem.charPos(); + unsigned int nGraphemeCount = 0; + + // Count grapheme clusters in the ligatures. + while (nPos < aGlyphItem.charPos() + aGlyphItem.charCount()) + { + nPos = xBreak->nextCharacters(rStr, nPos, aLocale, + css::i18n::CharacterIteratorMode::SKIPCELL, 1, nDone); + nGraphemeCount++; + } + + // Set the width of each grapheme cluster. + nPos = aGlyphItem.charPos(); + auto nWidth = aGlyphItem.newWidth() / nGraphemeCount; + // rounding difference + auto nDiff = aGlyphItem.newWidth() - (nWidth * nGraphemeCount); + for (unsigned int i = 0; i < nGraphemeCount; i++) + { + rCharWidths[nPos - mnMinCharPos] += nWidth; + // add rounding difference to last component to maintain + // ligature width. + if (i == nGraphemeCount - 1) + rCharWidths[nPos - mnMinCharPos] += nDiff; + nPos = xBreak->nextCharacters(rStr, nPos, aLocale, + css::i18n::CharacterIteratorMode::SKIPCELL, 1, nDone); + } + } + else + rCharWidths[aGlyphItem.charPos() - mnMinCharPos] += aGlyphItem.newWidth(); } } @@ -665,7 +705,7 @@ void GenericSalLayout::ApplyDXArray(const double* pDXArray, const sal_Bool* pKas std::unique_ptr<double[]> const pNewCharWidths(new double[nCharCount]); // Get the natural character widths (i.e. before applying DX adjustments). - GetCharWidths(aOldCharWidths); + GetCharWidths(aOldCharWidths, {}); // Calculate the character widths after DX adjustments. for (int i = 0; i < nCharCount; ++i) diff --git a/vcl/source/gdi/sallayout.cxx b/vcl/source/gdi/sallayout.cxx index eca07148ed53..6d22b302b6c5 100644 --- a/vcl/source/gdi/sallayout.cxx +++ b/vcl/source/gdi/sallayout.cxx @@ -133,6 +133,7 @@ sal_UCS4 GetLocalizedChar( sal_UCS4 nChar, LanguageType eLang ) SalLayout::SalLayout() : mnMinCharPos( -1 ), mnEndCharPos( -1 ), + maLanguageTag( LANGUAGE_DONTKNOW ), mnUnitsPerPixel( 1 ), mnOrientation( 0 ), maDrawOffset( 0, 0 ), @@ -147,6 +148,7 @@ void SalLayout::AdjustLayout( vcl::text::ImplLayoutArgs& rArgs ) mnMinCharPos = rArgs.mnMinCharPos; mnEndCharPos = rArgs.mnEndCharPos; mnOrientation = rArgs.mnOrientation; + maLanguageTag = rArgs.maLanguageTag; } DevicePoint SalLayout::GetDrawPosition(const DevicePoint& rRelative) const @@ -263,10 +265,10 @@ SalLayoutGlyphs SalLayout::GetGlyphs() const return SalLayoutGlyphs(); // invalid } -DeviceCoordinate GenericSalLayout::FillDXArray( std::vector<DeviceCoordinate>* pCharWidths ) const +DeviceCoordinate GenericSalLayout::FillDXArray( std::vector<DeviceCoordinate>* pCharWidths, const OUString& rStr ) const { if (pCharWidths) - GetCharWidths(*pCharWidths); + GetCharWidths(*pCharWidths, rStr); return GetTextWidth(); } @@ -494,7 +496,7 @@ void GenericSalLayout::GetCaretPositions( int nMaxIndex, sal_Int32* pCaretXArray sal_Int32 GenericSalLayout::GetTextBreak( DeviceCoordinate nMaxWidth, DeviceCoordinate nCharExtra, int nFactor ) const { std::vector<DeviceCoordinate> aCharWidths; - GetCharWidths(aCharWidths); + GetCharWidths(aCharWidths, {}); DeviceCoordinate nWidth = 0; for( int i = mnMinCharPos; i < mnEndCharPos; ++i ) @@ -675,7 +677,7 @@ void MultiSalLayout::AdjustLayout( vcl::text::ImplLayoutArgs& rArgs ) mpLayouts[n]->SalLayout::AdjustLayout( aMultiArgs ); // then we can measure the unmodified metrics int nCharCount = rArgs.mnEndCharPos - rArgs.mnMinCharPos; - FillDXArray( &aJustificationArray ); + FillDXArray( &aJustificationArray, {} ); // #i17359# multilayout is not simplified yet, so calculating the // unjustified width needs handholding; also count the number of // stretchable virtual char widths @@ -1039,12 +1041,12 @@ sal_Int32 MultiSalLayout::GetTextBreak( DeviceCoordinate nMaxWidth, DeviceCoordi int nCharCount = mnEndCharPos - mnMinCharPos; std::vector<DeviceCoordinate> aCharWidths; std::vector<DeviceCoordinate> aFallbackCharWidths; - mpLayouts[0]->FillDXArray( &aCharWidths ); + mpLayouts[0]->FillDXArray( &aCharWidths, {} ); for( int n = 1; n < mnLevel; ++n ) { SalLayout& rLayout = *mpLayouts[ n ]; - rLayout.FillDXArray( &aFallbackCharWidths ); + rLayout.FillDXArray( &aFallbackCharWidths, {} ); double fUnitMul = mnUnitsPerPixel; fUnitMul /= rLayout.GetUnitsPerPixel(); for( int i = 0; i < nCharCount; ++i ) @@ -1070,7 +1072,7 @@ sal_Int32 MultiSalLayout::GetTextBreak( DeviceCoordinate nMaxWidth, DeviceCoordi return -1; } -DeviceCoordinate MultiSalLayout::FillDXArray( std::vector<DeviceCoordinate>* pCharWidths ) const +DeviceCoordinate MultiSalLayout::FillDXArray( std::vector<DeviceCoordinate>* pCharWidths, const OUString& rStr ) const { DeviceCoordinate nMaxWidth = 0; @@ -1086,7 +1088,7 @@ DeviceCoordinate MultiSalLayout::FillDXArray( std::vector<DeviceCoordinate>* pCh for( int n = mnLevel; --n >= 0; ) { // query every fallback level - DeviceCoordinate nTextWidth = mpLayouts[n]->FillDXArray( &aTempWidths ); + DeviceCoordinate nTextWidth = mpLayouts[n]->FillDXArray( &aTempWidths, rStr ); if( !nTextWidth ) continue; // merge results from current level diff --git a/vcl/source/outdev/text.cxx b/vcl/source/outdev/text.cxx index 0cf0283dc5c7..d5969c7a0092 100644 --- a/vcl/source/outdev/text.cxx +++ b/vcl/source/outdev/text.cxx @@ -889,7 +889,7 @@ tools::Long OutputDevice::GetTextWidth( const OUString& rStr, sal_Int32 nIndex, { tools::Long nWidth = GetTextArray( rStr, nullptr, nIndex, - nLen, pLayoutCache, pSalLayoutCache ); + nLen, false, pLayoutCache, pSalLayoutCache ); return nWidth; } @@ -956,7 +956,7 @@ void OutputDevice::DrawTextArray( const Point& rStartPt, const OUString& rStr, } tools::Long OutputDevice::GetTextArray( const OUString& rStr, std::vector<sal_Int32>* pDXAry, - sal_Int32 nIndex, sal_Int32 nLen, + sal_Int32 nIndex, sal_Int32 nLen, bool bCaret, vcl::text::TextLayoutCache const*const pLayoutCache, SalLayoutGlyphs const*const pSalLayoutCache) const { @@ -994,7 +994,7 @@ tools::Long OutputDevice::GetTextArray( const OUString& rStr, std::vector<sal_In xDXPixelArray.reset(new std::vector<DeviceCoordinate>(nLen)); } std::vector<DeviceCoordinate>* pDXPixelArray = xDXPixelArray.get(); - DeviceCoordinate nWidth = pSalLayout->FillDXArray(pDXPixelArray); + DeviceCoordinate nWidth = pSalLayout->FillDXArray(pDXPixelArray, bCaret ? rStr : OUString()); int nWidthFactor = pSalLayout->GetUnitsPerPixel(); // convert virtual char widths to virtual absolute positions @@ -1039,7 +1039,7 @@ tools::Long OutputDevice::GetTextArray( const OUString& rStr, std::vector<sal_In #else /* ! VCL_FLOAT_DEVICE_PIXEL */ - tools::Long nWidth = pSalLayout->FillDXArray( pDXAry ); + tools::Long nWidth = pSalLayout->FillDXArray( pDXAry, bCaret ? rStr : OUString() ); int nWidthFactor = pSalLayout->GetUnitsPerPixel(); // convert virtual char widths to virtual absolute positions