officecfg/registry/schema/org/openoffice/Office/Writer.xcs | 7 ++ sw/source/core/layout/frmtool.cxx | 4 + sw/source/core/text/frmpaint.cxx | 33 +++++++++---- sw/source/core/text/porlin.hxx | 5 + sw/source/core/text/portxt.cxx | 18 ++++++- 5 files changed, 56 insertions(+), 11 deletions(-)
New commits: commit 8a9aa8096e360856f911ae1011bc42fa70c4b8e4 Author: László Németh <nem...@numbertext.org> AuthorDate: Mon Jun 30 13:17:01 2025 +0200 Commit: Christian Lohmaier <lohmaier+libreoff...@googlemail.com> CommitDate: Tue Aug 26 00:04:36 2025 +0200 tdf#167298 tdf#166113 sw word spacing: add word spacing indicator tdf#167298: As an advanced feature for typography and related development – including bug fixing –, add visualization for justified text lines with too big word spacing. tdf#166113: fix bInteropSmartJustify by adding bFullJustified to avoid unnecessary extra calculation for the last paragraph line. (Regression since commit 529755f0919217a84a12daad0fddfddd1124f0e9 "tdf#166113 sw smart justify: adjust algorithm for interoperability", detected by the word spacing indicator). The red word spacing indicator line is positioned before the justified text line, and its width is the extra space of a space character over the maximum word spacing. For example, if the font-defined space width is 3 pt, the maximum word spacing is 150%, spaces expanded to 6 pt in the justified line results a 6 pt - 3 pt * 1.5 = 1.5 pt width word spacing indicator. To enable the word spacing indicator in a justified text, 1) Open "Tools" -> Options" -> "Advanced" -> "Open Expert Configuration"; 2) search for "ShowWordSpacingIndicator"; 3) double-click on the config entry (or press Enter) to set it to True; 4) Enable Formatting Marks (Control-F10). Follow-up to commit 5a48070f5904c51dc9e7bbad4213d802fd4bc89b "tdf#126154 sw offapi xmloff cui: add min/max word spacing" and commit 7d6696757dcdfa3cee481ac7795a91b2b47da363 "tdf#159923 sw cui offapi xmloff: add custom word spacing". Change-Id: I315b5e6b8239f4fdc8e69eb45fc292b8d5891138 Reviewed-on: https://gerrit.libreoffice.org/c/core/+/187199 Reviewed-by: László Németh <nem...@numbertext.org> Tested-by: Jenkins (cherry picked from commit c05bc38067b2ab61ae919fb1fef1381343d03844) Reviewed-on: https://gerrit.libreoffice.org/c/core/+/187735 Reviewed-by: Ilmari Lauhakangas <ilmari.lauhakan...@libreoffice.org> Reviewed-by: Christian Lohmaier <lohmaier+libreoff...@googlemail.com> diff --git a/officecfg/registry/schema/org/openoffice/Office/Writer.xcs b/officecfg/registry/schema/org/openoffice/Office/Writer.xcs index 8fe6aa00847b..880323bdcd75 100644 --- a/officecfg/registry/schema/org/openoffice/Office/Writer.xcs +++ b/officecfg/registry/schema/org/openoffice/Office/Writer.xcs @@ -867,6 +867,13 @@ </info> <value>false</value> </prop> + <prop oor:name="ShowWordSpacingIndicator" oor:type="xs:boolean" oor:nillable="false"> + <info> + <desc>Enables the word spacing indicator, which shows before each justified text line how much the expanded space in the current line exceeds the maximum word spacing set for the paragraph.</desc> + <label>Show word spacing indicator at justified lines.</label> + </info> + <value>false</value> + </prop> <prop oor:name="ShowWarningHiddenSection" oor:type="xs:boolean" oor:nillable="false"> <!-- UIHints: Not accessible via user interface --> <info> diff --git a/sw/source/core/layout/frmtool.cxx b/sw/source/core/layout/frmtool.cxx index aeff3829d603..be2f33c0a050 100644 --- a/sw/source/core/layout/frmtool.cxx +++ b/sw/source/core/layout/frmtool.cxx @@ -80,6 +80,7 @@ #include <frameformats.hxx> #include <boost/circular_buffer.hpp> #include <svx/sdr/attribute/sdrallfillattributeshelper.hxx> +#include <officecfg/Office/Writer.hxx> using namespace ::com::sun::star; @@ -3989,6 +3990,9 @@ bool IsExtraData( const SwDoc& rDoc ) return true; } + if ( officecfg::Office::Writer::Content::Display::ShowWordSpacingIndicator::get() ) + return true; + const SwEditShell* pSh = rDoc.GetEditShell(); const SwViewOption* pViewOptions = pSh ? pSh->GetViewOptions() : nullptr; return pViewOptions && pViewOptions->IsShowOutlineContentVisibilityButton(); diff --git a/sw/source/core/text/frmpaint.cxx b/sw/source/core/text/frmpaint.cxx index f768d212a62f..6e0c77adc090 100644 --- a/sw/source/core/text/frmpaint.cxx +++ b/sw/source/core/text/frmpaint.cxx @@ -43,6 +43,8 @@ #include <tabfrm.hxx> #include <numrule.hxx> #include <wrong.hxx> +#include <vcl/lineinfo.hxx> +#include <officecfg/Office/Writer.hxx> #include <EnhancedPDFExportHelper.hxx> @@ -96,7 +98,7 @@ public: } void PaintExtra( SwTwips nY, tools::Long nAsc, tools::Long nMax, bool bRed, const OUString* pRedlineText = nullptr ); - void PaintRedline( SwTwips nY, tools::Long nMax ); + void PaintRedline( SwTwips nY, tools::Long nMax, sal_Int16 nWordSpacing = 0 ); }; } @@ -279,7 +281,8 @@ void SwExtraPainter::PaintExtra( SwTwips nY, tools::Long nAsc, tools::Long nMax, } } -void SwExtraPainter::PaintRedline( SwTwips nY, tools::Long nMax ) +// paint redline or word spacing indicator +void SwExtraPainter::PaintRedline( SwTwips nY, tools::Long nMax, sal_Int16 nWordSpacing ) { Point aStart( m_nRedX, nY ); Point aEnd( m_nRedX, nY + nMax ); @@ -295,15 +298,18 @@ void SwExtraPainter::PaintRedline( SwTwips nY, tools::Long nMax ) } } const Color aOldCol( m_pSh->GetOut()->GetLineColor() ); - m_pSh->GetOut()->SetLineColor(SwModule::get()->GetRedlineMarkColor()); + m_pSh->GetOut()->SetLineColor(nWordSpacing ? COL_LIGHTRED : SwModule::get()->GetRedlineMarkColor()); - if ( m_pTextFrame->IsVertical() ) + if ( nWordSpacing ) { - m_pTextFrame->SwitchHorizontalToVertical( aStart ); - m_pTextFrame->SwitchHorizontalToVertical( aEnd ); - } + LineInfo aLineInfo; + aLineInfo.SetStyle(LineStyle::Solid); + aLineInfo.SetWidth( nWordSpacing * 2540/1440 ); - m_pSh->GetOut()->DrawLine( aStart, aEnd ); + m_pSh->GetOut()->DrawLine( aStart, aEnd, aLineInfo ); + } + else + m_pSh->GetOut()->DrawLine( aStart, aEnd ); m_pSh->GetOut()->SetLineColor( aOldCol ); } @@ -321,19 +327,22 @@ void SwTextFrame::PaintExtraData( const SwRect &rRect ) const bool bLineNum = !IsInTab() && rLineInf.IsPaintLineNumbers() && ( !IsInFly() || rLineInf.IsCountInFlys() ) && rLineNum.IsCount(); sal_Int16 eHor = static_cast<sal_Int16>(SwModule::get()->GetRedlineMarkPos()); + SwViewShell *pSh = getRootFrame()->GetCurrShell(); + bool bWordSpacingIndicator = officecfg::Office::Writer::Content::Display::ShowWordSpacingIndicator::get() + && pSh->GetViewOptions()->IsViewMetaChars(); if (eHor != text::HoriOrientation::NONE + && !bWordSpacingIndicator && (!IDocumentRedlineAccess::IsShowChanges(rIDRA.GetRedlineFlags()) || getRootFrame()->IsHideRedlines())) { eHor = text::HoriOrientation::NONE; } bool bRedLine = eHor != text::HoriOrientation::NONE; - if ( !bLineNum && !bRedLine ) + if ( !bLineNum && !bRedLine && !bWordSpacingIndicator ) return; if( IsLocked() || IsHiddenNow() || !getFramePrintArea().Height() ) return; - SwViewShell *pSh = getRootFrame()->GetCurrShell(); SwSwapIfNotSwapped swap(const_cast<SwTextFrame *>(this)); SwRect rOldRect( rRect ); @@ -388,6 +397,10 @@ void SwTextFrame::PaintExtraData( const SwRect &rRect ) const && aLine.GetStart() >= TextFrameIndex(aLine.GetTextFrame()->GetText().getLength()))) { + SwTwips nExtraSpaceSize = aLine.GetCurr()->GetFirstPortion()->ExtraSpaceSize(); + if ( nExtraSpaceSize && bWordSpacingIndicator ) + aExtra.PaintRedline( aLine.Y(), aLine.GetLineHeight(), nExtraSpaceSize ); + bool bRed = bRedLine && aLine.GetCurr()->HasRedline(); if( rLineInf.IsCountBlankLines() || aLine.GetCurr()->HasContent() ) { diff --git a/sw/source/core/text/porlin.hxx b/sw/source/core/text/porlin.hxx index ff3f571b72e6..eb160830c1d4 100644 --- a/sw/source/core/text/porlin.hxx +++ b/sw/source/core/text/porlin.hxx @@ -51,6 +51,7 @@ private: bool m_bIsFieldmarkText; SwTwips m_nExtraBlankWidth = 0; // width of spaces after the break SwTwips m_nExtraShrunkWidth = 0; // width of not shrunk line + SwTwips m_nExtraSpaceSize = 0; // extra space over normal space width std::optional<SwLinePortionLayoutContext> m_nLayoutContext; @@ -77,6 +78,8 @@ public: void ExtraBlankWidth(const SwTwips nNew) { m_nExtraBlankWidth = nNew; } SwTwips ExtraShrunkWidth() const { return m_nExtraShrunkWidth; } void ExtraShrunkWidth(const SwTwips nNew) { m_nExtraShrunkWidth = nNew; } + SwTwips ExtraSpaceSize() const { return m_nExtraSpaceSize; } + void ExtraSpaceSize(const SwTwips nNew) { m_nExtraSpaceSize = nNew; } SwTwips GetHangingBaseline() const { return mnHangingBaseline; } void SetHangingBaseline( const SwTwips nNewBaseline ) { mnHangingBaseline = nNewBaseline; } const std::optional<SwLinePortionLayoutContext> & GetLayoutContext() const { return m_nLayoutContext; } @@ -193,6 +196,7 @@ inline SwLinePortion &SwLinePortion::operator=(const SwLinePortion &rPortion) m_bJoinBorderWithNext = rPortion.m_bJoinBorderWithNext; m_nExtraBlankWidth = rPortion.m_nExtraBlankWidth; m_nExtraShrunkWidth = rPortion.m_nExtraShrunkWidth; + m_nExtraSpaceSize = rPortion.m_nExtraSpaceSize; m_nLayoutContext = rPortion.m_nLayoutContext; return *this; } @@ -209,6 +213,7 @@ inline SwLinePortion::SwLinePortion(const SwLinePortion &rPortion) : m_bIsFieldmarkText( rPortion.m_bIsFieldmarkText ), m_nExtraBlankWidth(rPortion.m_nExtraBlankWidth), m_nExtraShrunkWidth(rPortion.m_nExtraShrunkWidth), + m_nExtraSpaceSize(rPortion.m_nExtraSpaceSize), m_nLayoutContext(rPortion.m_nLayoutContext) { } diff --git a/sw/source/core/text/portxt.cxx b/sw/source/core/text/portxt.cxx index ddc063709210..40473f8458d3 100644 --- a/sw/source/core/text/portxt.cxx +++ b/sw/source/core/text/portxt.cxx @@ -304,6 +304,7 @@ void SwTextPortion::BreakCut( SwTextFormatInfo &rInf, const SwTextGuess &rGuess SetLen( TextFrameIndex(0) ); Width( 0 ); ExtraShrunkWidth( 0 ); + ExtraSpaceSize( 0 ); } } @@ -313,6 +314,7 @@ void SwTextPortion::BreakUnderflow( SwTextFormatInfo &rInf ) Height( 0 ); Width( 0 ); ExtraShrunkWidth( 0 ); + ExtraSpaceSize( 0 ); SetLen( TextFrameIndex(0) ); SetAscent( 0 ); rInf.SetUnderflow( this ); @@ -358,6 +360,7 @@ bool SwTextPortion::Format_( SwTextFormatInfo &rInf ) } ExtraShrunkWidth( 0 ); + ExtraSpaceSize( 0 ); std::optional<SwTextGuess> pGuess(std::in_place); bool bFull = !pGuess->Guess( *this, rInf, Height() ); @@ -367,7 +370,7 @@ bool SwTextPortion::Format_( SwTextFormatInfo &rInf ) const SvxAdjust aAdjust = aAdjustItem.GetAdjust(); bool bFullJustified = bFull && aAdjust == SvxAdjust::Block && pGuess->BreakPos() != TextFrameIndex(COMPLETE_STRING); - bool bInteropSmartJustify = + bool bInteropSmartJustify = bFullJustified && rInf.GetTextFrame()->GetDoc().getIDocumentSettingAccess().get( DocumentSettingId::JUSTIFY_LINES_WITH_SHRINKING); bool bNoWordSpacing = aAdjustItem.GetPropWordSpacing() == 100 && @@ -418,6 +421,9 @@ bool SwTextPortion::Format_( SwTextFormatInfo &rInf ) sal_Int32 nRealSpaces = rInf.GetLineSpaceCount( pGuess->BreakPos() ); float fSpaceNormal = (rInf.GetLineWidth() - (rInf.GetBreakWidth() - nRealSpaces * nSpaceWidth/10.0))/nRealSpaces; + float fExpansionOverMax = fSpaceNormal - nSpaceWidth/10.0 * aAdjustItem.GetPropWordSpacingMaximum()/100.0; + ExtraSpaceSize( rInf.GetBreakWidth() > rInf.GetLineWidth()/2 && fExpansionOverMax > 0 ? fExpansionOverMax : 0); + bool bOrigHyphenated = pGuess->HyphWord().is() && pGuess->BreakPos() > rInf.GetLineStart(); // calculate line breaking with desired word spacing, also @@ -431,7 +437,11 @@ bool SwTextPortion::Format_( SwTextFormatInfo &rInf ) sal_Int32 nSpacesInLine2 = rInf.GetLineSpaceCount( pGuess->BreakPos() ); if ( rInf.GetBreakWidth() <= rInf.GetLineWidth() ) + { fSpaceNormal = (rInf.GetLineWidth() - (rInf.GetBreakWidth() - nSpacesInLine2 * nSpaceWidth/10.0))/nSpacesInLine2; + fExpansionOverMax = fSpaceNormal - nSpaceWidth/10.0 * aAdjustItem.GetPropWordSpacingMaximum()/100.0; + ExtraSpaceSize( rInf.GetBreakWidth() > rInf.GetLineWidth()/2 && fExpansionOverMax > 0 ? fExpansionOverMax : 0); + } } sal_Int32 nSpacesInLineShrink = 0; @@ -499,12 +509,14 @@ bool SwTextPortion::Format_( SwTextFormatInfo &rInf ) if ( z1 >= z0 || bIsPortion ) { pGuess = std::move(pGuess2); + ExtraSpaceSize(0); bFull = bFull2; } } else if ( bOldInterop ) { pGuess = std::move(pGuess2); + ExtraSpaceSize(0); bFull = bFull2; } } @@ -514,7 +526,10 @@ bool SwTextPortion::Format_( SwTextFormatInfo &rInf ) } if ( pGuess->BreakWidth() != nOldWidth ) + { ExtraShrunkWidth( pGuess->BreakWidth() ); + ExtraSpaceSize( 0 ); + } } } @@ -692,6 +707,7 @@ bool SwTextPortion::Format( SwTextFormatInfo &rInf ) Height( 0 ); Width( 0 ); ExtraShrunkWidth( 0 ); + ExtraSpaceSize( 0 ); SetLen( TextFrameIndex(0) ); SetAscent( 0 ); SetNextPortion( nullptr ); // ????