cool#16071 sw: do not lose footnote lines cut away to fit

RearrangeFootnotes may cause SetRstHeight to set the rest height to 0 for
footnote lines no more fitting the footnote area; but 0 was used as a
special value in IsInside, meaning "no rest height was set", which then
incorrectly measured the upper instead - still as tall as the lines the
frame had before the truncation. Then the frame was considered as having
enough space, marked valid, and the lines cut away from it got no follow to
move to, so they never appeared anywhere.

Give the rest height its own "not given" state instead of spelling that as
a number, and honour a height of zero for what it says. A magic negative
would not do: SwContentFrame::WouldFit_ passes a negative height when the
frames before it overfill the new upper, and that has to keep meaning that
no line fits. The footnote computation in the constructor still falls back
to the upper when it ends up at or below zero, so only the SetRstHeight
path changes behaviour.

Signed-off-by: Mike Kaganski <mike.kaganski@collabora.com>
Change-Id: If692577f3ee692ad5c0dbe87f9a11a2670f8cf16
Reviewed-on: https://gerrit.collaboraoffice.com/c/online/+/8906
Tested-by: Jenkins CPCI <releng@collaboraoffice.com>
Reviewed-by: Miklos Vajna <vmiklos@collabora.com>
(cherry picked from commit 77634c741bb538853a757c2a41e4c9b919f22815)
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/209271
Reviewed-by: Xisco Fauli <xiscofauli@libreoffice.org>
Tested-by: Jenkins
This commit is contained in:
Mike Kaganski
2026-08-19 00:40:59 +02:00
committed by Xisco Fauli
parent 50ce488d18
commit 44c92622d2
5 changed files with 123 additions and 22 deletions
@@ -0,0 +1,60 @@
<?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:table="urn:oasis:names:tc:opendocument:xmlns:table:1.0" xmlns:fo="urn:oasis:names:tc:opendocument:xmlns:xsl-fo-compatible:1.0" xmlns:svg="urn:oasis:names:tc:opendocument:xmlns:svg-compatible:1.0" office:version="1.3" office:mimetype="application/vnd.oasis.opendocument.text">
<office:font-face-decls>
<style:font-face style:name="Liberation Serif" svg:font-family="&apos;Liberation Serif&apos;" style:font-family-generic="roman"/>
</office:font-face-decls>
<office:automatic-styles>
<style:style style:name="Tbl" style:family="table">
<style:table-properties style:width="8cm" table:align="left" style:may-break-between-rows="true"/>
</style:style>
<style:style style:name="TblCol" style:family="table-column">
<style:table-column-properties style:column-width="8cm"/>
</style:style>
<style:style style:name="Row1" style:family="table-row">
<style:table-row-properties style:row-height="5.8cm"/>
</style:style>
<style:style style:name="Row2" style:family="table-row">
<style:table-row-properties style:row-height="2cm"/>
</style:style>
<style:style style:name="Row3" style:family="table-row">
<style:table-row-properties style:row-height="3cm"/>
</style:style>
<style:style style:name="Row4" style:family="table-row">
<style:table-row-properties style:row-height="2cm"/>
</style:style>
<style:style style:name="PFtn" style:family="paragraph" style:parent-style-name="Footnote">
<style:text-properties style:font-name="Liberation Serif" fo:font-size="10pt"/>
</style:style>
<style:style style:name="Cell" style:family="table-cell">
<style:table-cell-properties fo:padding="0cm" fo:border="0.5pt solid #000000"/>
</style:style>
<style:style style:name="P1" style:family="paragraph" style:parent-style-name="Standard">
<style:text-properties style:font-name="Liberation Serif" fo:font-size="12pt"/>
</style:style>
<style:page-layout style:name="PM1">
<style:page-layout-properties fo:page-width="10cm" fo:page-height="8cm" style:print-orientation="portrait" fo:margin-top="1cm" fo:margin-bottom="1cm" fo:margin-left="1cm" fo:margin-right="1cm"/>
</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>
<table:table table:name="T1" table:style-name="Tbl">
<table:table-column table:style-name="TblCol"/>
<table:table-row table:style-name="Row1">
<table:table-cell table:style-name="Cell" office:value-type="string"><text:p text:style-name="P1">Row 1<text:note text:id="ftn1" text:note-class="footnote"><text:note-citation>1</text:note-citation><text:note-body><text:p text:style-name="PFtn">alpha bravo charlie delta echo foxtrot golf hotel india juliet kilo lima mike november oscar papa quebec romeo sierra tango uniform victor whiskey xray yankee zulu alpha bravo charlie delta echo foxtrot golf hotel india juliet kilo lima mike november oscar papa quebec romeo sierra tango uniform victor whiskey xray yankee zulu alpha bravo charlie delta echo foxtrot golf hotel india juliet kilo lima mike november oscar papa quebec romeo sierra tango uniform victor whiskey xray yankee zulu alpha bravo charlie delta echo foxtrot golf hotel india juliet filler</text:p></text:note-body></text:note></text:p></table:table-cell>
</table:table-row>
<table:table-row table:style-name="Row2">
<table:table-cell table:style-name="Cell" office:value-type="string"><text:p text:style-name="P1">Row 2</text:p></table:table-cell>
</table:table-row>
<table:table-row table:style-name="Row3">
<table:table-cell table:style-name="Cell" office:value-type="string"><text:p text:style-name="P1">Row 3</text:p></table:table-cell>
</table:table-row>
<table:table-row table:style-name="Row4">
<table:table-cell table:style-name="Cell" office:value-type="string"><text:p text:style-name="P1">Row 4<text:note text:id="ftn2" text:note-class="footnote"><text:note-citation>2</text:note-citation><text:note-body><text:p text:style-name="PFtn">alpha bravo charlie delta echo foxtrot golf hotel india juliet kilo lima mike november oscar papa quebec romeo sierra TAILMARK</text:p></text:note-body></text:note></text:p></table:table-cell>
</table:table-row>
</table:table>
</office:text>
</office:body>
</office:document>
+33
View File
@@ -15,6 +15,7 @@
#include <com/sun/star/text/WrapTextMode.hpp>
#include <com/sun/star/text/XTextSectionsSupplier.hpp>
#include <vcl/event.hxx>
#include <vcl/pdf/PDFPageObjectType.hxx>
#include <vcl/scheduler.hxx>
#include <editeng/fontitem.hxx>
#include <editeng/fhgtitem.hxx>
@@ -1610,6 +1611,38 @@ CPPUNIT_TEST_FIXTURE(SwLayoutWriter6, testCool16067_footnoteSpaceFreedBySplit)
assertXPath(pXmlDoc, "//page[4]/body/tab/row", 1);
}
CPPUNIT_TEST_FIXTURE(SwLayoutWriter6, testCool16071_footnoteTailKeptWhenTruncated)
{
// The first row's footnote fills the footnote area of the page it ends up on, and the last
// row travels between pages carrying a footnote of three lines. That leaves the lines after
// the first formatted against a footnote area with no room for them.
createSwDoc("Cool16071_footnoteTailKeptWhenTruncated.fodt");
// A layout dump would format what it reads, and that alone repairs the frame: measure the
// export instead.
save(TestFilter::PDF_WRITER);
std::unique_ptr<vcl::pdf::PDFiumDocument> pPdf = parsePDFExport();
if (!pPdf)
return;
// the last word of the last footnote, on its third line
bool bTailExported = false;
for (int nPage = 0; nPage < pPdf->getPageCount() && !bTailExported; ++nPage)
{
std::unique_ptr<vcl::pdf::PDFiumPage> pPage = pPdf->openPage(nPage);
std::unique_ptr<vcl::pdf::PDFiumTextPage> pTextPage = pPage->getTextPage();
for (int i = 0; i < pPage->getObjectCount() && !bTailExported; ++i)
{
std::unique_ptr<vcl::pdf::PDFiumPageObject> pObject = pPage->getObject(i);
if (pObject->getType() == vcl::pdf::PDFPageObjectType::Text)
bTailExported = pObject->getText(pTextPage).indexOf("TAILMARK") >= 0;
}
}
// Without the fix the footnote kept its first line only: the two lines cut away from it had
// nowhere to go, so their text was missing from the document altogether.
CPPUNIT_ASSERT(bTailExported);
}
CPPUNIT_TEST_FIXTURE(SwLayoutWriter6, testHiddenParagraphFollowFrame)
{
createSwDoc("hidden-para-follow-frame.fodt");
+1 -1
View File
@@ -1849,7 +1849,7 @@ void SwTextFrame::Format_( SwTextFormatter &rLine, SwTextFormatInfo &rInf,
rRepaint.Top( rLine.Y() );
if( 0 >= rRepaint.Width() )
rRepaint.Width(1);
WidowsAndOrphans aFrameBreak( this, rInf.IsTest() ? 1 : 0 );
WidowsAndOrphans aFrameBreak(this, rInf.IsTest() ? std::optional<SwTwips>(0) : std::nullopt);
// rLine is now set to the first line which needs formatting.
// The bFirst flag makes sure that Next() is not called.
+21 -17
View File
@@ -59,8 +59,9 @@ bool IsNastyFollow( const SwTextFrame *pFrame )
}
SwTextFrameBreak::SwTextFrameBreak( SwTextFrame *pNewFrame, const SwTwips nRst )
: m_nRstHeight(nRst), m_pFrame(pNewFrame)
SwTextFrameBreak::SwTextFrameBreak(SwTextFrame* pNewFrame, const std::optional<SwTwips>& oRst)
: m_oRstHeight(oRst)
, m_pFrame(pNewFrame)
{
SwSwapIfSwapped swap(m_pFrame);
SwRectFnSet aRectFnSet(*m_pFrame);
@@ -84,13 +85,13 @@ SwTextFrameBreak::SwTextFrameBreak( SwTextFrame *pNewFrame, const SwTwips nRst )
m_bBreak = false;
if( !m_nRstHeight && !m_pFrame->IsFollow() && m_pFrame->IsInFootnote() && m_pFrame->HasPara() )
if (!m_oRstHeight && !m_pFrame->IsFollow() && m_pFrame->IsInFootnote() && m_pFrame->HasPara())
{
m_nRstHeight = m_pFrame->GetFootnoteFrameHeight();
m_nRstHeight += aRectFnSet.GetHeight(m_pFrame->getFramePrintArea()) -
aRectFnSet.GetHeight(m_pFrame->getFrameArea());
if( m_nRstHeight < 0 )
m_nRstHeight = 0;
m_oRstHeight = m_pFrame->GetFootnoteFrameHeight()
+ aRectFnSet.GetHeight(m_pFrame->getFramePrintArea())
- aRectFnSet.GetHeight(m_pFrame->getFrameArea());
if (*m_oRstHeight <= 0)
m_oRstHeight.reset();
}
}
@@ -144,8 +145,8 @@ bool SwTextFrameBreak::IsInside(SwTextMargin const& rLine, SwResizeLimitReason&
&& rLine.GetEnd() >= TextFrameIndex(m_pFrame->GetText().getLength()))
nLineHeight += m_pFrame->CalcAddLowerSpaceAsLastInTableCell();
if( m_nRstHeight )
bFit = m_nRstHeight >= nLineHeight;
if (m_oRstHeight)
bFit = *m_oRstHeight >= nLineHeight;
else
{
// The Frame has a height to fit on the page.
@@ -297,22 +298,25 @@ void SwTextFrameBreak::SetRstHeight( const SwTextMargin &rLine )
// Consider bottom margin
SwRectFnSet aRectFnSet(*m_pFrame);
m_nRstHeight = aRectFnSet.GetBottomMargin(*m_pFrame);
m_oRstHeight = aRectFnSet.GetBottomMargin(*m_pFrame);
if ( aRectFnSet.IsVert() )
{
if ( m_pFrame->IsVertLR() )
m_nRstHeight = aRectFnSet.YDiff( m_pFrame->SwitchHorizontalToVertical( rLine.Y() ) , m_nOrigin );
m_oRstHeight
= aRectFnSet.YDiff(m_pFrame->SwitchHorizontalToVertical(rLine.Y()), m_nOrigin);
else
m_nRstHeight += m_nOrigin - m_pFrame->SwitchHorizontalToVertical( rLine.Y() );
*m_oRstHeight += m_nOrigin - m_pFrame->SwitchHorizontalToVertical(rLine.Y());
}
else
m_nRstHeight += rLine.Y() - m_nOrigin;
*m_oRstHeight += rLine.Y() - m_nOrigin;
}
WidowsAndOrphans::WidowsAndOrphans( SwTextFrame *pNewFrame, const SwTwips nRst,
bool bChkKeep )
: SwTextFrameBreak( pNewFrame, nRst ), m_nWidLines( 0 ), m_nOrphLines( 0 )
WidowsAndOrphans::WidowsAndOrphans(SwTextFrame* pNewFrame, const std::optional<SwTwips>& oRst,
bool bChkKeep)
: SwTextFrameBreak(pNewFrame, oRst)
, m_nWidLines(0)
, m_nOrphLines(0)
{
SwSwapIfSwapped swap(m_pFrame);
+8 -4
View File
@@ -18,6 +18,8 @@
*/
#pragma once
#include <optional>
#include <swtypes.hxx>
#include "itrtxt.hxx"
@@ -26,14 +28,16 @@ class SwTextFrame;
class SwTextFrameBreak
{
private:
SwTwips m_nRstHeight;
// The height the frame has to fit into. Zero and negative values are considered valid. If not
// set, the whole upper is measured instead.
std::optional<SwTwips> m_oRstHeight;
SwTwips m_nOrigin;
protected:
SwTextFrame *m_pFrame;
bool m_bBreak;
bool m_bKeep;
public:
SwTextFrameBreak( SwTextFrame *pFrame, const SwTwips nRst = 0 );
SwTextFrameBreak(SwTextFrame* pFrame, const std::optional<SwTwips>& oRst = std::nullopt);
bool IsBreakNow( SwTextMargin &rLine );
bool IsKeepAlways() const { return m_bKeep; }
@@ -56,8 +60,8 @@ private:
sal_uInt16 m_nWidLines, m_nOrphLines;
public:
WidowsAndOrphans( SwTextFrame *pFrame, const SwTwips nRst = 0,
bool bCheckKeep = true );
WidowsAndOrphans(SwTextFrame* pFrame, const std::optional<SwTwips>& oRst = std::nullopt,
bool bCheckKeep = true);
bool FindWidows( SwTextFrame *pFrame, SwTextMargin &rLine );
sal_uInt16 GetOrphansLines() const
{ return m_nOrphLines; }