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 );  // ????

Reply via email to