sw: Inline headings produce incorrect order in TOC

Inline headings are stored as flys anchored at the start of the
paragraph.

`SwTOXSortTabBase::sort_lt` is responsible for sorting the content of
Table of Contents. Inline headings are not properly handled, which
results in incorrect ordering of outline elements in TOC.

Navigation panel also suffers with the same problem, where fly nodes
are always assumed to render after the anchor paragraph. Handle as-char
fly nodes which are positioned at the start of the anchor paragraph.

Change-Id: Id3339b85fbae7731cfc5620477def1057ca15e31
Signed-off-by: Karthik <karthik.godha@collabora.com>
Reviewed-on: https://gerrit.collaboraoffice.com/c/online/+/2596
Reviewed-by: Caolán McNamara <caolan.mcnamara@collabora.com>
Tested-by: Caolán McNamara <caolan.mcnamara@collabora.com>
(cherry picked from commit 745330e2d6f8c31193bdd10ba7e039a2aba6d6a8)
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/205781
Tested-by: Jenkins
Reviewed-by: Xisco Fauli <xiscofauli@libreoffice.org>
This commit is contained in:
Karthik
2026-05-28 09:33:59 +02:00
committed by Xisco Fauli
parent a24b03d274
commit 227aac0aa4
5 changed files with 150 additions and 5 deletions
@@ -0,0 +1,99 @@
<?xml version="1.0" encoding="UTF-8"?>
<office:document
xmlns:office="urn:oasis:names:tc:opendocument:xmlns:office:1.0"
xmlns:style="urn:oasis:names:tc:opendocument:xmlns:style:1.0"
xmlns:text="urn:oasis:names:tc:opendocument:xmlns:text:1.0"
xmlns:fo="urn:oasis:names:tc:opendocument:xmlns:xsl-fo-compatible:1.0"
xmlns:draw="urn:oasis:names:tc:opendocument:xmlns:drawing:1.0"
xmlns:svg="urn:oasis:names:tc:opendocument:xmlns:svg-compatible:1.0"
xmlns:xlink="http://www.w3.org/1999/xlink"
office:version="1.4"
office:mimetype="application/vnd.oasis.opendocument.text">
<office:font-face-decls>
<style:font-face style:name="Liberation Serif" svg:font-family="'Liberation Serif'"/>
<style:font-face style:name="Liberation Sans" svg:font-family="'Liberation Sans'"/>
</office:font-face-decls>
<office:styles>
<style:style style:name="Standard" style:family="paragraph" style:class="text"/>
<style:style style:name="Heading" style:family="paragraph" style:parent-style-name="Standard"
style:next-style-name="Standard" style:class="chapter">
<style:paragraph-properties fo:margin-top="0.423cm" fo:margin-bottom="0.212cm"
style:contextual-spacing="false" fo:keep-with-next="always"/>
<style:text-properties style:font-name="Liberation Sans" fo:font-size="14pt"/>
</style:style>
<style:style style:name="Heading_20_1" style:display-name="Heading 1" style:family="paragraph"
style:parent-style-name="Heading" style:default-outline-level="1" style:class="chapter">
<style:paragraph-properties fo:margin-top="0.423cm" fo:margin-bottom="0.212cm"
style:contextual-spacing="false"/>
<style:text-properties fo:font-size="18pt" fo:font-weight="bold"/>
</style:style>
<style:style style:name="Heading_20_2" style:display-name="Heading 2" style:family="paragraph"
style:parent-style-name="Heading" style:default-outline-level="2" style:class="chapter">
<style:paragraph-properties fo:margin-top="0.353cm" fo:margin-bottom="0.212cm"
style:contextual-spacing="false"/>
<style:text-properties fo:font-size="16pt" fo:font-weight="bold"/>
</style:style>
<style:style style:name="Contents_20_1" style:display-name="Contents 1" style:family="paragraph"
style:parent-style-name="Standard" style:class="index"/>
<style:style style:name="Contents_20_2" style:display-name="Contents 2" style:family="paragraph"
style:parent-style-name="Standard" style:class="index"/>
<style:style style:name="Contents_20_Heading" style:display-name="Contents Heading"
style:family="paragraph" style:parent-style-name="Heading" style:class="index">
<style:text-properties fo:font-size="16pt" fo:font-weight="bold"/>
</style:style>
<style:style style:name="Index_20_Link" style:display-name="Index Link" style:family="text"/>
<style:style style:name="Inline_20_Heading" style:display-name="Inline Heading" style:family="graphic">
<style:graphic-properties text:anchor-type="as-char"
svg:y="0cm" fo:margin-left="0cm" fo:margin-right="0cm"
style:vertical-pos="middle" style:vertical-rel="text"
fo:background-color="transparent" draw:fill="none"/>
</style:style>
</office:styles>
<office:automatic-styles>
<style:style style:name="P1" style:family="paragraph" style:parent-style-name="Heading_20_1">
<style:paragraph-properties fo:margin-top="0cm" fo:margin-bottom="0cm" style:contextual-spacing="false"/>
</style:style>
<style:style style:name="fr1" style:family="graphic" style:parent-style-name="Inline_20_Heading">
<style:graphic-properties style:vertical-pos="middle" style:vertical-rel="text"/>
</style:style>
<style:style style:name="Sect1" style:family="section">
<style:section-properties>
<style:columns fo:column-count="1" fo:column-gap="0cm"/>
</style:section-properties>
</style:style>
<style:page-layout style:name="pm1">
<style:page-layout-properties fo:page-width="21cm" fo:page-height="29.7cm"
fo:margin-top="2cm" fo:margin-bottom="2cm"
fo:margin-left="2cm" fo:margin-right="2cm"/>
</style:page-layout>
</office:automatic-styles>
<office:master-styles>
<style:master-page style:name="Standard" style:page-layout-name="pm1"/>
</office:master-styles>
<office:body>
<office:text>
<text:h text:style-name="Heading_20_1" text:outline-level="1">Foo</text:h>
<text:h text:style-name="Heading_20_2" text:outline-level="2"><draw:frame draw:style-name="fr1" draw:name="Frame1" text:anchor-type="as-char" draw:z-index="0"><draw:text-box><text:h text:style-name="P1" text:outline-level="1">Bar</text:h></draw:text-box></draw:frame> boo</text:h>
<text:table-of-content text:style-name="Sect1" text:protected="true" text:name="TOC1">
<text:table-of-content-source text:outline-level="10">
<text:index-title-template text:style-name="Contents_20_Heading">Table of Contents</text:index-title-template>
<text:table-of-content-entry-template text:outline-level="1" text:style-name="Contents_20_1">
<text:index-entry-link-start text:style-name="Index_20_Link"/>
<text:index-entry-text/>
<text:index-entry-link-end/>
</text:table-of-content-entry-template>
<text:table-of-content-entry-template text:outline-level="2" text:style-name="Contents_20_2">
<text:index-entry-link-start text:style-name="Index_20_Link"/>
<text:index-entry-text/>
<text:index-entry-link-end/>
</text:table-of-content-entry-template>
</text:table-of-content-source>
<text:index-body>
<text:index-title text:style-name="Sect1" text:name="TOC1_Head">
<text:p text:style-name="Contents_20_Heading">Table of Contents</text:p>
</text:index-title>
</text:index-body>
</text:table-of-content>
</office:text>
</office:body>
</office:document>
+16
View File
@@ -2236,6 +2236,22 @@ CPPUNIT_TEST_FIXTURE(SwLayoutWriter6, testTdf170846_2)
assertXPath(pXmlDoc, "//page[2]//tab", 3); // Three tables on page 2
}
CPPUNIT_TEST_FIXTURE(SwLayoutWriter6, testTocInlineHeadingOrder)
{
createSwDoc("toc-inline-heading-order.fodt");
dispatchCommand(mxComponent, u".uno:UpdateAllIndexes"_ustr, {});
xmlDocUniquePtr pXmlDoc = parseLayoutDump();
// The TOC is the second section in the body. The body section's three
// txt children carry the entries in document order.
assertXPath(pXmlDoc, "/root/page[1]/body/section[2]/txt[1]/SwParaPortion/SwLineLayout",
"portion", u"Foo");
assertXPath(pXmlDoc, "/root/page[1]/body/section[2]/txt[2]/SwParaPortion/SwLineLayout",
"portion", u"Bar");
assertXPath(pXmlDoc, "/root/page[1]/body/section[2]/txt[3]/SwParaPortion/SwLineLayout",
"portion", u" boo");
}
CPPUNIT_TEST_FIXTURE(SwLayoutWriter6, testInlineTableShiftDown)
{
createSwDoc("floattable-center-shift-down.docx");
+1
View File
@@ -162,6 +162,7 @@ struct SwTOXSortTabBase
private:
bool m_bValidText;
bool m_bAnchoredAsChar; // used by SwTOXSortTabBase::sort_lt to break ties
TextAndReading m_aSort;
virtual TextAndReading GetText_Impl(SwRootFrame const* pLayout) const = 0;
+16 -2
View File
@@ -36,6 +36,7 @@
#include <pam.hxx>
#include <txttxmrk.hxx>
#include <frmfmt.hxx>
#include <fmtanchr.hxx>
#include <fmtfld.hxx>
#include <txmsrt.hxx>
#include <ndtxt.hxx>
@@ -139,6 +140,7 @@ SwTOXSortTabBase::SwTOXSortTabBase( TOXSortType nTyp, const SwContentNode* pNd,
: pTOXNd( nullptr ), pTextMark( pMark ), pTOXIntl( pInter ),
nPos( 0 ), nCntPos( 0 ), nType( o3tl::narrowing<sal_uInt16>(nTyp) )
, m_bValidText( false )
, m_bAnchoredAsChar( false )
{
if ( pLocale )
aLocale = *pLocale;
@@ -177,6 +179,10 @@ SwTOXSortTabBase::SwTOXSortTabBase( TOXSortType nTyp, const SwContentNode* pNd,
OSL_ENSURE(bResult, "where is the text node");
nPos = aPos.GetNodeIndex();
nCntPos = aPos.GetContentIndex();
const SwFrameFormat* pFlyFormat = pNd->GetFlyFormat();
if (pFlyFormat && pFlyFormat->GetAnchor().GetAnchorId() == RndStdIds::FLY_AS_CHAR)
m_bAnchoredAsChar = true;
}
}
else
@@ -291,8 +297,16 @@ bool SwTOXSortTabBase::sort_lt(const SwTOXSortTabBase& rCmp)
}
else if( pFirst && pFirst->IsTextNode() &&
pNext && pNext->IsTextNode() )
return ::IsFrameBehind( *static_cast<const SwTextNode*>(pNext), nCntPos,
*static_cast<const SwTextNode*>(pFirst), nCntPos );
{
// An inline (as-char) anchored fly's content is rendered
// at its anchor character position. If the character offset is same
// then the inline fly is rendered before the paragraph.
if (m_bAnchoredAsChar != rCmp.m_bAnchoredAsChar)
return m_bAnchoredAsChar;
return ::IsFrameBehind(*static_cast<const SwTextNode*>(pNext), nCntPos,
*static_cast<const SwTextNode*>(pFirst), nCntPos);
}
}
}
return false;
+18 -3
View File
@@ -580,10 +580,25 @@ void SwContentType::FillMemberList(bool* pbContentChanged)
if (nLevel >= m_nOutlineLevel)
continue;
double nYPos = m_bAlphabeticSort ? 0 : static_cast<double>(getYPos(*pNode));
if (nEndOfExtrasIndex >= pNode->GetIndex() && pNode->GetFlyFormat())
const SwFrameFormat* pFlyFormat = pNode->GetFlyFormat();
if (nEndOfExtrasIndex >= pNode->GetIndex() && pFlyFormat)
{
nYPos += nOutlinesInFramesIndexAdjustment;
nOutlinesInFramesIndexAdjustment += 0.00001;
// For fly nodes getYPos() returns their anchor node's nYPos
const SwFormatAnchor& rAnchor = pFlyFormat->GetAnchor();
const SwPosition* pAnchorPos = rAnchor.GetContentAnchor();
if (rAnchor.GetAnchorId() == RndStdIds::FLY_AS_CHAR && pAnchorPos
&& pAnchorPos->GetContentIndex() == 0)
{
// An inline (as-char) fly anchored at the start of its
// anchor paragraph renders before the paragraph's own
// text. Place its outline entry before the anchor's nYPos.
nYPos -= 0.0001;
}
else
{
nYPos += nOutlinesInFramesIndexAdjustment;
nOutlinesInFramesIndexAdjustment += 0.00001;
}
}
auto pCnt(std::make_unique<SwOutlineContent>(this, aEntry, i, nLevel,