sw/qa/extras/ooxmlexport/data/tdf163894.docx        |binary
 sw/qa/extras/ooxmlexport/data/tdf163894_hidden.docx |binary
 sw/qa/extras/ooxmlexport/ooxmlexport14.cxx          |   60 ++++++
 sw/source/core/fields/reffld.cxx                    |  173 ++++++++++++++++++--
 4 files changed, 217 insertions(+), 16 deletions(-)

New commits:
commit da88bd54eba4bb3c5a588c211d0233a8eef77ffb
Author:     László Németh <[email protected]>
AuthorDate: Tue Feb 10 10:27:52 2026 +0100
Commit:     László Németh <[email protected]>
CommitDate: Wed Feb 18 13:52:35 2026 +0100

    tdf#163894 sw DOCX: (p)refer char style-ref on the actual page
    
    When a paragraph starts before the page, choose
    the referred text on the actual page for the header/
    footer references. If there is no such text, choose
    the nearest one on the previous pages, in the case
    of the backward search, too, according to the
    interoperability. If there is no other text, choose
    the nearest referred text on the next pages.
    
    Follow-up to commit fce6492756e4657008ed1ce734a08929aa19c264
    "tdf#163894 sw DOCX: add character styles to style-ref window",
    commit 955f0f9b5e7f1d4ba42eb314478cc6924b4b63a7
    "tdf#163894 sw DOCX: fix style-ref with character style",
    commit d4fdafa103bfea94a279d7069ddc50ba92f67d01 "tdf#160402
    writerfilter,sw: STYLEREF field can refer to character style"
    and commit 32c588dd1164aa2fc4c8120ddb74bd510cc082f9
    "tdf#86790: Add support for a word-style styleref".
    
    Change-Id: I2e9dc239202b281989f3d1b1a182e55127f2c419
    Reviewed-on: https://gerrit.libreoffice.org/c/core/+/199591
    Reviewed-by: László Németh <[email protected]>
    Tested-by: Jenkins

diff --git a/sw/qa/extras/ooxmlexport/data/tdf163894.docx 
b/sw/qa/extras/ooxmlexport/data/tdf163894.docx
new file mode 100644
index 000000000000..99b654efcf25
Binary files /dev/null and b/sw/qa/extras/ooxmlexport/data/tdf163894.docx differ
diff --git a/sw/qa/extras/ooxmlexport/data/tdf163894_hidden.docx 
b/sw/qa/extras/ooxmlexport/data/tdf163894_hidden.docx
new file mode 100644
index 000000000000..1c75658afcfb
Binary files /dev/null and 
b/sw/qa/extras/ooxmlexport/data/tdf163894_hidden.docx differ
diff --git a/sw/qa/extras/ooxmlexport/ooxmlexport14.cxx 
b/sw/qa/extras/ooxmlexport/ooxmlexport14.cxx
index 8e1730693e81..12a3930ef9c2 100644
--- a/sw/qa/extras/ooxmlexport/ooxmlexport14.cxx
+++ b/sw/qa/extras/ooxmlexport/ooxmlexport14.cxx
@@ -792,6 +792,66 @@ CPPUNIT_TEST_FIXTURE(Test, testTdf155707)
     assertXPath(pXmlDoc, "/w:document/w:body/w:p[17]/w:pPr/w:jc", "val", 
u"highKashida");
 }
 
+CPPUNIT_TEST_FIXTURE(Test, testTdf163894)
+{
+    createSwDoc("tdf163894.docx");
+    save(TestFilter::DOCX);
+
+    xmlDocUniquePtr pLayout = parseLayoutDump();
+    assertXPath(pLayout, 
"/root/page[1]/header/txt[1]/SwParaPortion/SwLineLayout/SwFieldPortion[1]",
+                "expand", u"handbooks");
+    assertXPath(pLayout, 
"/root/page[1]/header/txt[1]/SwParaPortion/SwLineLayout/SwFieldPortion[2]",
+                "expand", u"infuriating");
+    assertXPath(pLayout, 
"/root/page[2]/header/txt[1]/SwParaPortion/SwLineLayout/SwFieldPortion[1]",
+                "expand", u"infuriating");
+
+    // backward search reaches the whole content of the first node on the page,
+    // i.e. which content was typeset on the previous pages, like here
+    assertXPath(pLayout, 
"/root/page[2]/header/txt[1]/SwParaPortion/SwLineLayout/SwFieldPortion[2]",
+                "expand", u"infuriating");
+
+    assertXPath(pLayout, 
"/root/page[3]/header/txt[1]/SwParaPortion/SwLineLayout/SwFieldPortion[1]",
+                "expand", u"initializes");
+    assertXPath(pLayout, 
"/root/page[3]/header/txt[1]/SwParaPortion/SwLineLayout/SwFieldPortion[2]",
+                "expand", u"misfitting");
+    assertXPath(pLayout, 
"/root/page[4]/header/txt[1]/SwParaPortion/SwLineLayout/SwFieldPortion[1]",
+                "expand", u"misrepresenting");
+    assertXPath(pLayout, 
"/root/page[4]/header/txt[1]/SwParaPortion/SwLineLayout/SwFieldPortion[2]",
+                "expand", u"modicum");
+}
+
+CPPUNIT_TEST_FIXTURE(Test, testTdf163894_hidden)
+{
+    createSwDoc("tdf163894_hidden.docx");
+    save(TestFilter::DOCX);
+
+    xmlDocUniquePtr pLayout = parseLayoutDump();
+    assertXPath(pLayout, 
"/root/page[1]/header/txt[1]/SwParaPortion/SwLineLayout/SwFieldPortion[1]",
+                "expand", u"handbooks");
+    assertXPath(pLayout, 
"/root/page[1]/header/txt[1]/SwParaPortion/SwLineLayout/SwFieldPortion[2]",
+                "expand", u"infuriating");
+
+    // there is a single paragraph on the page: choose the nearest reference 
from the previous pages,
+    // not the nearest one on the following pages
+    assertXPath(pLayout, 
"/root/page[2]/header/txt[1]/SwParaPortion/SwLineLayout/SwFieldPortion[1]",
+                "expand", u"infuriating");
+    assertXPath(pLayout, 
"/root/page[2]/header/txt[1]/SwParaPortion/SwLineLayout/SwFieldPortion[2]",
+                "expand", u"infuriating");
+
+    // hiding the field content by a hidden space formatted with the referred 
character style,
+    // separated by an also hidden space without the referred characters style 
from the other
+    // marching text content
+    assertXPath(pLayout, 
"/root/page[3]/header/txt[1]/SwParaPortion/SwLineLayout/SwFieldPortion[1]",
+                "expand", u" ");
+    assertXPath(pLayout, 
"/root/page[3]/header/txt[1]/SwParaPortion/SwLineLayout/SwFieldPortion[2]",
+                "expand", u" ");
+
+    assertXPath(pLayout, 
"/root/page[4]/header/txt[1]/SwParaPortion/SwLineLayout/SwFieldPortion[1]",
+                "expand", u"mitosis");
+    assertXPath(pLayout, 
"/root/page[4]/header/txt[1]/SwParaPortion/SwLineLayout/SwFieldPortion[2]",
+                "expand", u"modicum");
+}
+
 CPPUNIT_TEST_FIXTURE(Test, testTdf161643)
 {
     createSwDoc("fdo76163.docx");
diff --git a/sw/source/core/fields/reffld.cxx b/sw/source/core/fields/reffld.cxx
index 480f283cf840..f05c8edd93c3 100644
--- a/sw/source/core/fields/reffld.cxx
+++ b/sw/source/core/fields/reffld.cxx
@@ -1294,7 +1294,10 @@ namespace
     SwTextNode* SearchForStyleAnchor(const SwTextNode* pSelf, SwNode* pCurrent,
                                     std::u16string_view rStyleName,
                                     sal_Int32 *const pStart, sal_Int32 *const 
pEnd,
-                                    bool bCaseSensitive = true)
+                                    TextFrameIndex nStartOffset = 
TextFrameIndex(0),
+                                    TextFrameIndex nEndOffset = 
TextFrameIndex(0),
+                                    bool bCaseSensitive = true,
+                                    bool bSearchBackward = false)
     {
         if (pCurrent == pSelf)
             return nullptr;
@@ -1316,8 +1319,67 @@ namespace
             return pTextNode;
         }
 
+        bool bHasEndOffset = true;
+        if ( nEndOffset == TextFrameIndex(0) )
+        {
+            // search referred character formatting backward
+            // to get the last one in the node
+            nEndOffset = TextFrameIndex(pTextNode->GetText().getLength());
+            bHasEndOffset = false;
+        }
+
         bool bHasHint = false;
-        if (auto const pHints = pTextNode->GetpSwpHints())
+        auto const pHints = pTextNode->GetpSwpHints();
+        // first search backward
+        if ( pHints && bSearchBackward )
+        {
+            for (size_t i = pHints->Count(); i > 0; --i)
+            {
+                auto const*const pHint(pHints->Get(i - 1));
+                if (pHint->Which() == RES_TXTATR_CHARFMT)
+                {
+                    // not on the actual page yet
+                    if ( TextFrameIndex(pHint->GetStart()) > nEndOffset )
+                    {
+                        continue;
+                    }
+
+                    if (bCaseSensitive
+                        ? 
pHint->GetCharFormat().GetCharFormat()->HasName(rStyleName)
+                        : 
pHint->GetCharFormat().GetCharFormat()->GetName().toString().equalsIgnoreAsciiCase(rStyleName))
+                    {
+                        // if the recent hint (started before the previous 
one) is not adjacent,
+                        // return with the first start stored in pStart, i.e. 
with the last
+                        // continuous page text in the paragraph, which is 
formatted
+                        // with the requested character style
+                        if ( bHasHint && ( !pEnd || *pHint->End() != *pStart ) 
)
+                        {
+                            if ( TextFrameIndex(*pStart) < nEndOffset )
+                            {
+                                // found a reference on the page
+                                return pTextNode;
+                            }
+                            else
+                                // the previous was a reference after the page:
+                                // start a new one
+                                bHasHint = false;
+                        }
+
+                        if ( !bHasHint )
+                        {
+                            if (pEnd)
+                                *pEnd = *pHint->End();
+                            bHasHint = true;
+                        }
+
+                        *pStart = pHint->GetStart();
+                    }
+                }
+            }
+            if ( bHasHint && TextFrameIndex(*pStart) < nEndOffset && 
TextFrameIndex(*pStart) >= nStartOffset )
+                return pTextNode;
+        }
+        else if (pHints)
         {
             for (size_t i = 0, nCnt = pHints->Count(); i < nCnt; ++i)
             {
@@ -1334,10 +1396,26 @@ namespace
                             bHasHint = true;
                         }
                         // if the next hint is not adjacent, return with the 
last end stored in pEnd,
-                        // i.e. with the first continuous text in the 
paragraph, which is formatted
+                        // i.e. with the first continuous page text in the 
paragraph, which is formatted
                         // with the requested character style
                         else if ( !pEnd || *pEnd != pHint->GetStart() )
-                            return pTextNode;
+                        {
+                            // return with the previous matching hint, if
+                            // 1) the previous matching hint was already on 
the page OR
+                            // 2) there is only a single paragraph on the page 
and
+                            //    the recent hint is there after the page
+                            if ( pEnd && ( TextFrameIndex(*pEnd) > 
nStartOffset  ||
+                                    ( bHasEndOffset &&
+                                      TextFrameIndex(pHint->GetStart()) > 
nEndOffset ) ) )
+                            {
+                                return pTextNode;
+                            }
+                            else
+                            {
+                                // start new matching
+                                *pStart = pHint->GetStart();
+                            }
+                        }
 
                         if (pEnd)
                         {
@@ -1356,24 +1434,52 @@ namespace
     SwTextNode* SearchForStyleAnchor(const SwTextNode* pSelf, const SwNodes& 
rNodes, SwNodeOffset nNodeStart, SwNodeOffset nNodeEnd, bool bSearchBackward,
                                     std::u16string_view rStyleName,
                                     sal_Int32 *const pStart, sal_Int32 *const 
pEnd,
+                                    TextFrameIndex nPageStartOffset = 
TextFrameIndex(0),
+                                    TextFrameIndex nPageEndOffset = 
TextFrameIndex(0),
                                     bool bCaseSensitive = true)
     {
         if (!bSearchBackward)
         {
-            for (SwNodeOffset nCurrent = nNodeStart; nCurrent <= nNodeEnd; 
++nCurrent)
+            SwNodeOffset nCurrent = nNodeStart;
+            // first node can have an offset for character styles, check that 
first
+            if ( nPageStartOffset != TextFrameIndex(0) )
+            {
+                SwNode* pCurrent = rNodes[nCurrent];
+                SwTextNode* pFound = SearchForStyleAnchor(pSelf, pCurrent, 
rStyleName, pStart, pEnd, nPageStartOffset,
+                                nNodeStart == nNodeEnd ? nPageEndOffset : 
TextFrameIndex(0), bCaseSensitive);
+
+                if (pFound)
+                    return pFound;
+            }
+
+            for (; nCurrent <= nNodeEnd; ++nCurrent)
             {
                 SwNode* pCurrent = rNodes[nCurrent];
-                SwTextNode* pFound = SearchForStyleAnchor(pSelf, pCurrent, 
rStyleName, pStart, pEnd, bCaseSensitive);
+                SwTextNode* pFound = SearchForStyleAnchor(pSelf, pCurrent, 
rStyleName, pStart, pEnd, TextFrameIndex(0), TextFrameIndex(0), bCaseSensitive);
                 if (pFound)
                     return pFound;
             }
         }
         else
         {
-            for (SwNodeOffset nCurrent = nNodeEnd; nCurrent >= nNodeStart; 
--nCurrent)
+            SwNodeOffset nCurrent = nNodeEnd;
+
+            // first end node can have an offset for character styles, check 
that first
+            if ( nPageEndOffset != TextFrameIndex(0) )
+            {
+                SwNode* pCurrent = rNodes[nCurrent];
+                SwTextNode* pFound = SearchForStyleAnchor(pSelf, pCurrent, 
rStyleName, pStart, pEnd, nPageStartOffset, nPageEndOffset, bCaseSensitive, 
bSearchBackward);
+                if (pFound)
+                    return pFound;
+                // continue with the last but one paragraph on the page
+                if ( nCurrent >= nNodeStart )
+                    --nCurrent;
+            }
+
+            for (; nCurrent >= nNodeStart; --nCurrent)
             {
                 SwNode* pCurrent = rNodes[nCurrent];
-                SwTextNode* pFound = SearchForStyleAnchor(pSelf, pCurrent, 
rStyleName, pStart, pEnd, bCaseSensitive);
+                SwTextNode* pFound = SearchForStyleAnchor(pSelf, pCurrent, 
rStyleName, pStart, pEnd, TextFrameIndex(0), TextFrameIndex(0), bCaseSensitive, 
bSearchBackward);
                 if (pFound)
                     return pFound;
             }
@@ -1605,6 +1711,8 @@ SwTextNode* 
SwGetRefFieldType::FindAnchorRefStyleMarginal(SwDoc* pDoc,
 
     const SwNode* pPageStart(nullptr);
     const SwNode* pPageEnd(nullptr);
+    TextFrameIndex nPageStartOffset(0);
+    TextFrameIndex nPageEndOffset(0);
 
     if (pPageFrame)
     {
@@ -1617,6 +1725,10 @@ SwTextNode* 
SwGetRefFieldType::FindAnchorRefStyleMarginal(SwDoc* pDoc,
             {
                 pPageStart = static_cast<const SwTextFrame*>(pPageStartFrame)
                                 ->GetTextNodeFirst();
+
+                // style-refs referring character style need offset data
+                nPageStartOffset = static_cast<const 
SwTextFrame*>(pPageStartFrame)
+                                ->GetOffset();
             }
             else
             {
@@ -1631,6 +1743,35 @@ SwTextNode* 
SwGetRefFieldType::FindAnchorRefStyleMarginal(SwDoc* pDoc,
             {
                 pPageEnd = static_cast<const SwTextFrame*>(pPageEndFrame)
                             ->GetTextNodeFirst();
+
+                // style-refs referring character style need offset data
+
+                // set page end offset to the last character of the paragraph
+                auto pPage = static_cast<const SwTextFrame*>(pPageEndFrame);
+                nPageEndOffset = TextFrameIndex(pPage->GetText().getLength());
+
+                // adjust the end offset, if the paragraph continues on the 
next page
+                const SwPageFrame* pNextPage =
+                        static_cast<const SwPageFrame*>(pPageFrame->GetNext());
+                if ( pNextPage )
+                {
+                    // the get the page end offset, when the paragraph is 
continued on the
+                    // next page, check the first text frame of the next page
+                    const SwContentFrame* pNextPageStart =
+                            pNextPage->FindFirstBodyContent();
+                    if ( pNextPageStart && pNextPageStart->IsTextFrame() )
+                    {
+                        auto pNextPageTextNode = static_cast<const 
SwTextFrame*>(pNextPageStart)
+                           ->GetTextNodeFirst();
+                        if ( pNextPageTextNode == pPageEnd )
+                        {
+                           nPageEndOffset = static_cast<const 
SwTextFrame*>(pNextPageStart)
+                                ->GetOffset();
+                           // set end offset to the end of the first page
+                           nPageEndOffset--;
+                        }
+                    }
+                }
             }
             else
             {
@@ -1649,34 +1790,34 @@ SwTextNode* 
SwGetRefFieldType::FindAnchorRefStyleMarginal(SwDoc* pDoc,
     SwNodeOffset nPageEnd = pPageEnd->GetIndex();
     const SwNodes& nodes = pDoc->GetNodes();
 
-    pTextNd = SearchForStyleAnchor(pSelf, nodes, nPageStart, nPageEnd, 
bFlagFromBottom, styleName, pStart, pEnd);
+    pTextNd = SearchForStyleAnchor(pSelf, nodes, nPageStart, nPageEnd, 
bFlagFromBottom, styleName, pStart, pEnd, nPageStartOffset, nPageEndOffset);
     if (pTextNd)
         return pTextNd;
 
     // 2. Search up from the top of the page
-    pTextNd = SearchForStyleAnchor(pSelf, nodes, SwNodeOffset(0), nPageStart - 
1, /*bBackwards*/true, styleName, pStart, pEnd);
+    pTextNd = SearchForStyleAnchor(pSelf, nodes, SwNodeOffset(0), nPageStart - 
1, /*bBackwards*/true, styleName, pStart, pEnd, nPageStartOffset, 
nPageEndOffset);
     if (pTextNd)
         return pTextNd;
 
     // 3. Search down from the bottom of the page
-    pTextNd = SearchForStyleAnchor(pSelf, nodes, nPageEnd + 1, nodes.Count() - 
1, /*bBackwards*/false, styleName, pStart, pEnd);
+    pTextNd = SearchForStyleAnchor(pSelf, nodes, nPageEnd + 1, nodes.Count() - 
1, /*bBackwards*/false, styleName, pStart, pEnd, nPageStartOffset, 
nPageEndOffset);
     if (pTextNd)
         return pTextNd;
 
     // Word has case insensitive styles. LO has case sensitive styles. If we 
didn't find
     // it yet, maybe we could with a case insensitive search. Let's do that
 
-    pTextNd = SearchForStyleAnchor(pSelf, nodes, nPageStart, nPageEnd, 
bFlagFromBottom, styleName, pStart, pEnd,
+    pTextNd = SearchForStyleAnchor(pSelf, nodes, nPageStart, nPageEnd, 
bFlagFromBottom, styleName, pStart, pEnd, nPageStartOffset, nPageEndOffset,
                                    false /* bCaseSensitive */);
     if (pTextNd)
         return pTextNd;
 
-    pTextNd = SearchForStyleAnchor(pSelf, nodes, SwNodeOffset(0), nPageStart - 
1, /*bBackwards*/true, styleName, pStart, pEnd,
+    pTextNd = SearchForStyleAnchor(pSelf, nodes, SwNodeOffset(0), nPageStart - 
1, /*bBackwards*/true, styleName, pStart, pEnd, nPageStartOffset, 
nPageEndOffset,
                                    false /* bCaseSensitive */);
     if (pTextNd)
         return pTextNd;
 
-    pTextNd = SearchForStyleAnchor(pSelf, nodes, nPageEnd + 1, nodes.Count() - 
1, /*bBackwards*/false, styleName, pStart, pEnd,
+    pTextNd = SearchForStyleAnchor(pSelf, nodes, nPageEnd + 1, nodes.Count() - 
1, /*bBackwards*/false, styleName, pStart, pEnd, nPageStartOffset, 
nPageEndOffset,
                                    false /* bCaseSensitive */);
     return pTextNd;
 }
@@ -1720,12 +1861,12 @@ SwTextNode* 
SwGetRefFieldType::FindAnchorRefStyleOther(SwDoc* pDoc,
     // Again, we need to remember that Word styles are not case sensitive
 
     pTextNd = SearchForStyleAnchor(pSelf, nodes, SwNodeOffset(0), nReference, 
/*bBackwards*/true, styleName, pStart, pEnd,
-                                   false /* bCaseSensitive */);
+                                   TextFrameIndex(0), TextFrameIndex(0), false 
/* bCaseSensitive */);
     if (pTextNd)
         return pTextNd;
 
     pTextNd = SearchForStyleAnchor(pSelf, nodes, nReference + 1, nodes.Count() 
- 1, /*bBackwards*/false, styleName, pStart, pEnd,
-                                   false /* bCaseSensitive */);
+                                   TextFrameIndex(0), TextFrameIndex(0), false 
/* bCaseSensitive */);
     return pTextNd;
 }
 

Reply via email to